From c3f08dcb7266efeac84f5f720ec0a353a45e950d Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 9 Jun 2010 16:53:51 +0000 Subject: [PATCH] Merged Dirmngr with GnuPG. A few code changes to support dirmngr. --- ChangeLog | 18 + Makefile.am | 8 +- NEWS | 2 + agent/genkey.c | 3 +- am/cmacros.am | 6 +- autogen.sh | 2 +- common/ChangeLog | 20 +- common/asshelp.c | 3 +- common/exechelp-posix.c | 51 +- common/exechelp-w32.c | 105 +- common/exechelp-w32ce.c | 91 +- common/exechelp.h | 47 +- common/homedir.c | 93 +- common/logging.c | 15 +- common/logging.h | 2 +- common/util.h | 2 + configure.ac | 72 +- dirmngr/ChangeLog | 1345 ++++++++++++++++++++ dirmngr/ChangeLog.1 | 802 ++++++++++++ dirmngr/Makefile.am | 65 + dirmngr/OAUTHORS | 40 + dirmngr/ONEWS | 240 ++++ dirmngr/b64dec.c | 217 ++++ dirmngr/b64enc.c | 213 ++++ dirmngr/cdb.h | 91 ++ dirmngr/cdblib.c | 925 ++++++++++++++ dirmngr/certcache.c | 1384 +++++++++++++++++++++ dirmngr/certcache.h | 103 ++ dirmngr/crlcache.c | 2544 ++++++++++++++++++++++++++++++++++++++ dirmngr/crlcache.h | 70 ++ dirmngr/crlfetch.c | 479 +++++++ dirmngr/crlfetch.h | 93 ++ dirmngr/dirmngr-client.c | 1042 ++++++++++++++++ dirmngr/dirmngr.c | 1829 +++++++++++++++++++++++++++ dirmngr/dirmngr.h | 189 +++ dirmngr/dirmngr_ldap.c | 646 ++++++++++ dirmngr/get-path.c | 620 ++++++++++ dirmngr/http.c | 1861 ++++++++++++++++++++++++++++ dirmngr/http.h | 109 ++ dirmngr/ldap-url.c | 932 ++++++++++++++ dirmngr/ldap-url.h | 50 + dirmngr/ldap.c | 1499 ++++++++++++++++++++++ dirmngr/ldapserver.c | 133 ++ dirmngr/ldapserver.h | 90 ++ dirmngr/misc.c | 486 ++++++++ dirmngr/misc.h | 87 ++ dirmngr/no-libgcrypt.c | 154 +++ dirmngr/ocsp.c | 799 ++++++++++++ dirmngr/ocsp.h | 31 + dirmngr/server.c | 1539 +++++++++++++++++++++++ dirmngr/validate.c | 1160 +++++++++++++++++ dirmngr/validate.h | 55 + g13/be-encfs.c | 3 +- g13/runner.c | 6 +- m4/ChangeLog | 4 + m4/ldap.m4 | 3 +- scd/ChangeLog | 5 + scd/scdaemon.c | 11 +- sm/export.c | 3 +- sm/import.c | 3 +- tools/gpgconf-comp.c | 12 +- 61 files changed, 22335 insertions(+), 177 deletions(-) create mode 100644 dirmngr/ChangeLog create mode 100644 dirmngr/ChangeLog.1 create mode 100644 dirmngr/Makefile.am create mode 100644 dirmngr/OAUTHORS create mode 100644 dirmngr/ONEWS create mode 100644 dirmngr/b64dec.c create mode 100644 dirmngr/b64enc.c create mode 100644 dirmngr/cdb.h create mode 100644 dirmngr/cdblib.c create mode 100644 dirmngr/certcache.c create mode 100644 dirmngr/certcache.h create mode 100644 dirmngr/crlcache.c create mode 100644 dirmngr/crlcache.h create mode 100644 dirmngr/crlfetch.c create mode 100644 dirmngr/crlfetch.h create mode 100644 dirmngr/dirmngr-client.c create mode 100644 dirmngr/dirmngr.c create mode 100644 dirmngr/dirmngr.h create mode 100644 dirmngr/dirmngr_ldap.c create mode 100644 dirmngr/get-path.c create mode 100644 dirmngr/http.c create mode 100644 dirmngr/http.h create mode 100644 dirmngr/ldap-url.c create mode 100644 dirmngr/ldap-url.h create mode 100644 dirmngr/ldap.c create mode 100644 dirmngr/ldapserver.c create mode 100644 dirmngr/ldapserver.h create mode 100644 dirmngr/misc.c create mode 100644 dirmngr/misc.h create mode 100644 dirmngr/no-libgcrypt.c create mode 100644 dirmngr/ocsp.c create mode 100644 dirmngr/ocsp.h create mode 100644 dirmngr/server.c create mode 100644 dirmngr/validate.c create mode 100644 dirmngr/validate.h diff --git a/ChangeLog b/ChangeLog index 79d7a71ec..cc592590d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,23 @@ +2010-06-09 Werner Koch + + * configure.ac (GNUPG_DIRMNGR_LDAP_PGM): Add option + --with-dirmngr-ldap-pgm. + + * am/cmacros.am (-DGNUPG_LOCALSTATEDIR): New. + (GNUPG_DEFAULT_DIRMNGR_LDAP): New. + +2010-06-08 Werner Koch + + * configure.ac: Add build support for dirmngr. + (try_ldap): Rename to try_ks_ldap. + (GNUPG_CHECK_LDAP): Also test if dirmngr is to be build. + + * Makefile.am (SUBDIRS): Add dirmngr. + 2010-06-07 Werner Koch + * dirmngr/: New. + * configure.ac: Add option --enable-gpgtar. 2010-05-31 Werner Koch diff --git a/Makefile.am b/Makefile.am index a51b5ffd3..1bda6ffde 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,6 +61,11 @@ g13 = g13 else g13 = endif +if BUILD_DIRMNGR +dirmngr = dirmngr +else +dirmngr = +endif if BUILD_TOOLS tools = tools else @@ -79,7 +84,8 @@ tests = tests endif SUBDIRS = m4 gl include common ${kbx} \ - ${gpg} ${keyserver} ${sm} ${agent} ${scd} ${g13} ${tools} po ${doc} ${tests} + ${gpg} ${keyserver} ${sm} ${agent} ${scd} ${g13} ${dirmngr} \ + ${tools} po ${doc} ${tests} dist_doc_DATA = README diff --git a/NEWS b/NEWS index 6f39200d8..eb23bbd26 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,8 @@ Noteworthy changes in version 2.1.x (under development) option --use-standard-socket may now be used to use this feature by default. + * Dirmngr is now a part of this package. + Noteworthy changes in version 2.0.13 (2009-09-04) ------------------------------------------------- diff --git a/agent/genkey.c b/agent/genkey.c index 9e2f32480..c5d2c9e33 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -129,10 +129,11 @@ check_passphrase_pattern (ctrl_t ctrl, const char *pw) if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid)) result = 1; /* Execute error - assume password should no be used. */ - else if (gnupg_wait_process (pgmname, pid, NULL)) + else if (gnupg_wait_process (pgmname, pid, 0, NULL)) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ + gnupg_release_process (pid); /* Overwrite our temporary file. */ fseek (infp, 0, SEEK_SET); diff --git a/am/cmacros.am b/am/cmacros.am index da45a67b7..6668b2558 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -25,7 +25,8 @@ AM_CPPFLAGS += -DGNUPG_BINDIR="\"$(bindir)\"" \ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ - -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" + -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \ + -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" endif @@ -47,6 +48,9 @@ endif if GNUPG_PROTECT_TOOL_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" endif +if GNUPG_DIRMNGR_LDAP_PGM +AM_CPPFLAGS += -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +endif # Under Windows we use LockFileEx. WindowsCE provides this only on # the WindowsMobile 6 platform and thus we need to use the coredll6 diff --git a/autogen.sh b/autogen.sh index 092e35e8a..5dc8669e6 100755 --- a/autogen.sh +++ b/autogen.sh @@ -103,7 +103,7 @@ if [ "$myhost" = "w32" ]; then w32root="$w32ce_root" [ -z "$w32root" ] && w32root="$HOME/w32ce_root" toolprefixes="$w32ce_toolprefixes arm-mingw32ce" - extraoptions="--disable-scdaemon --disable-zip $w32ce_extraoptions" + extraoptions="--disable-scdaemon --disable-zip --disable-ldap --disable-dirmngr $w32ce_extraoptions" ;; *) [ -z "$w32root" ] && w32root="$HOME/w32root" diff --git a/common/ChangeLog b/common/ChangeLog index 5247415ce..849d1def9 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,21 @@ +2010-06-09 Werner Koch + + * exechelp-posix.c, exechelp-w32.c + * exechelp-w32ce.c (gnupg_wait_process): Add new arg HANG. Change + all callers. + (gnupg_release_process): New. Use it after all calls to + gnupg_wait_process. + + * util.h (GNUPG_MODULE_NAME_DIRMNGR_LDAP): New. + * homedir.c (gnupg_cachedir): New. + (w32_try_mkdir): New. + (dirmngr_socket_name): Chanmge standard socket name. + (gnupg_module_name): Support GNUPG_MODULE_NAME_DIRMNGR_LDAP. + + * logging.c (log_set_get_tid_callback): Replace by ... + (log_set_pid_suffix_cb): .. new. + (do_logv): Change accordingly. + 2010-06-08 Marcus Brinkmann * Makefile.am (AM_CFLAGS): Add $(LIBASSUAN_CFLAGS). @@ -5,7 +23,7 @@ * sysutils.c: Include . (translate_sys2libc_fd_int): Cast to silence gcc warning. * iobuf.c: Include - (translate_file_handle): Fix syntax error. + (translate_file_handle): Fix syntax error. 2010-06-08 Werner Koch diff --git a/common/asshelp.c b/common/asshelp.c index 95c774734..bd7aa8de9 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -362,12 +362,13 @@ start_new_gpg_agent (assuan_context_t *r_ctx, if (err) log_debug ("starting `%s' for testing failed: %s\n", agent_program, gpg_strerror (err)); - else if ((err = gnupg_wait_process (agent_program, pid, &excode))) + else if ((err = gnupg_wait_process (agent_program, pid, 0, &excode))) { if (excode == -1) log_debug ("running `%s' for testing failed: %s\n", agent_program, gpg_strerror (err)); } + gnupg_release_process (pid); if (!err && !excode) { diff --git a/common/exechelp-posix.c b/common/exechelp-posix.c index 1f4cca6c8..5a8e0289c 100644 --- a/common/exechelp-posix.c +++ b/common/exechelp-posix.c @@ -416,37 +416,39 @@ gnupg_spawn_process_fd (const char *pgmname, const char *argv[], } -/* Wait for the process identified by PID to terminate. PGMNAME should - be the same as supplied to the spawn function and is only used for - diagnostics. Returns 0 if the process succeeded, GPG_ERR_GENERAL - for any failures of the spawned program or other error codes. If - EXITCODE is not NULL the exit code of the process is stored at this - address or -1 if it could not be retrieved and no error message is - logged. */ +/* See exechelp.h for the description. */ gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) +gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) { gpg_err_code_t ec; - int i, status; - if (exitcode) - *exitcode = -1; + if (r_exitcode) + *r_exitcode = -1; if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); #ifdef USE_GNU_PTH - i = pth_waitpid ? pth_waitpid (pid, &status, 0) : waitpid (pid, &status, 0); -#else - while ( (i=waitpid (pid, &status, 0)) == -1 && errno == EINTR) - ; + if (pth_waitpid) + i = pth_waitpid (pid, &status, hang? 0:WNOHANG); + else #endif + { + while ((i=waitpid (pid, &status, hang? 0:WNOHANG)) == (pid_t)(-1) + && errno == EINTR) + ; + } + if (i == (pid_t)(-1)) { + ec = gpg_err_code_from_errno (errno); log_error (_("waiting for process %d to terminate failed: %s\n"), (int)pid, strerror (errno)); - ec = gpg_err_code_from_errno (errno); + } + else if (!i) + { + ec = GPG_ERR_TIMEOUT; /* Still running. */ } else if (WIFEXITED (status) && WEXITSTATUS (status) == 127) { @@ -455,11 +457,11 @@ gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) } else if (WIFEXITED (status) && WEXITSTATUS (status)) { - if (!exitcode) + if (!r_exitcode) log_error (_("error running `%s': exit status %d\n"), pgmname, WEXITSTATUS (status)); else - *exitcode = WEXITSTATUS (status); + *r_exitcode = WEXITSTATUS (status); ec = GPG_ERR_GENERAL; } else if (!WIFEXITED (status)) @@ -469,8 +471,8 @@ gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) } else { - if (exitcode) - *exitcode = 0; + if (r_exitcode) + *r_exitcode = 0; ec = 0; } @@ -478,7 +480,14 @@ gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) } -/* Spawn a new process and immediatley detach from it. The name of +void +gnupg_release_process (pid_t pid) +{ + (void)pid; +} + + +/* Spawn a new process and immediately detach from it. The name of the program to exec is PGMNAME and its arguments are in ARGV (the programname is automatically passed as first argument). Environment strings in ENVP are set. An error is returned if diff --git a/common/exechelp-w32.c b/common/exechelp-w32.c index 4616bec33..297f6f854 100644 --- a/common/exechelp-w32.c +++ b/common/exechelp-w32.c @@ -382,7 +382,7 @@ gnupg_spawn_process (const char *pgmname, const char *argv[], int cr_flags; char *cmdline; int fd, fdout, rp[2]; - HANDLE nullhd[]; + HANDLE nullhd[2]; int i; (void)preexec; @@ -428,7 +428,7 @@ gnupg_spawn_process (const char *pgmname, const char *argv[], } nullhd[0] = fd == -1? w32_open_null (0) : INVALID_HANDLE_VALUE; - nullhd[1] = outfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE; + nullhd[1] = fdout == -1? w32_open_null (1) : INVALID_HANDLE_VALUE; /* Start the process. Note that we can't run the PREEXEC function because this would change our own environment. */ @@ -437,7 +437,7 @@ gnupg_spawn_process (const char *pgmname, const char *argv[], si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; si.hStdInput = fd == -1? nullhd[0] : fd_to_handle (fd); - si.hStdOutput = outfd == -1? nullhd[1] : fd_to_handle (fdout); + si.hStdOutput = fdout == -1? nullhd[1] : fd_to_handle (fdout); si.hStdError = fd_to_handle (rp[1]); cr_flags = (CREATE_DEFAULT_ERROR_MODE @@ -599,22 +599,17 @@ gnupg_spawn_process_fd (const char *pgmname, const char *argv[], } -/* Wait for the process identified by PID to terminate. PGMNAME should - be the same as supplied to the spawn function and is only used for - diagnostics. Returns 0 if the process succeeded, GPG_ERR_GENERAL - for any failures of the spawned program or other error codes. If - EXITCODE is not NULL the exit code of the process is stored at this - address or -1 if it could not be retrieved. */ +/* See exechelp.h for a description. */ gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) +gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) { gpg_err_code_t ec; HANDLE proc = fd_to_handle (pid); int code; DWORD exc; - if (exitcode) - *exitcode = -1; + if (r_exitcode) + *r_exitcode = -1; if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); @@ -622,50 +617,66 @@ gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) /* FIXME: We should do a pth_waitpid here. However this has not yet been implemented. A special W32 pth system call would even be better. */ - code = WaitForSingleObject (proc, INFINITE); + code = WaitForSingleObject (proc, hang? INFINITE : 0); switch (code) { - case WAIT_FAILED: - log_error (_("waiting for process %d to terminate failed: %s\n"), - (int)pid, w32_strerror (-1)); - ec = GPG_ERR_GENERAL; - break; + case WAIT_TIMEOUT: + ec = GPG_ERR_TIMEOUT; + break; - case WAIT_OBJECT_0: - if (!GetExitCodeProcess (proc, &exc)) - { - log_error (_("error getting exit code of process %d: %s\n"), - (int)pid, w32_strerror (-1) ); - ec = GPG_ERR_GENERAL; - } - else if (exc) - { - log_error (_("error running `%s': exit status %d\n"), - pgmname, (int)exc ); - if (exitcode) - *exitcode = (int)exc; - ec = GPG_ERR_GENERAL; - } - else - { - if (exitcode) - *exitcode = 0; - ec = 0; - } - CloseHandle (proc); - break; + case WAIT_FAILED: + log_error (_("waiting for process %d to terminate failed: %s\n"), + (int)pid, w32_strerror (-1)); + ec = GPG_ERR_GENERAL; + break; - default: - log_error ("WaitForSingleObject returned unexpected " - "code %d for pid %d\n", code, (int)pid ); - ec = GPG_ERR_GENERAL; - break; + case WAIT_OBJECT_0: + if (!GetExitCodeProcess (proc, &exc)) + { + log_error (_("error getting exit code of process %d: %s\n"), + (int)pid, w32_strerror (-1) ); + ec = GPG_ERR_GENERAL; + } + else if (exc) + { + log_error (_("error running `%s': exit status %d\n"), + pgmname, (int)exc ); + if (r_exitcode) + *r_exitcode = (int)exc; + ec = GPG_ERR_GENERAL; + } + else + { + if (r_exitcode) + *r_exitcode = 0; + ec = 0; + } + break; + + default: + log_error ("WaitForSingleObject returned unexpected " + "code %d for pid %d\n", code, (int)pid ); + ec = GPG_ERR_GENERAL; + break; } - + return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); } + +void +gnupg_release_process (pid_t pid) +{ + if (pid != (pid_t)INVALID_HANDLE_VALUE) + { + HANDLE process = (HANDLE)pid; + + CloseHandle (process); + } +} + + /* Spawn a new process and immediatley detach from it. The name of the program to exec is PGMNAME and its arguments are in ARGV (the programname is automatically passed as first argument). diff --git a/common/exechelp-w32ce.c b/common/exechelp-w32ce.c index d206052b5..5a84c9f1d 100644 --- a/common/exechelp-w32ce.c +++ b/common/exechelp-w32ce.c @@ -653,14 +653,10 @@ gnupg_spawn_process_fd (const char *pgmname, const char *argv[], return 0; } -/* Wait for the process identified by PID to terminate. PGMNAME should - be the same as supplied to the spawn function and is only used for - diagnostics. Returns 0 if the process succeeded, GPG_ERR_GENERAL - for any failures of the spawned program or other error codes. If - EXITCODE is not NULL the exit code of the process is stored at this - address or -1 if it could not be retrieved. */ + +/* See exechelp.h for a description. */ gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) +gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *exitcode) { gpg_err_code_t ec; HANDLE proc = fd_to_handle (pid); @@ -676,50 +672,65 @@ gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) /* FIXME: We should do a pth_waitpid here. However this has not yet been implemented. A special W32 pth system call would even be better. */ - code = WaitForSingleObject (proc, INFINITE); + code = WaitForSingleObject (proc, hang? INFINITE : 0); switch (code) { - case WAIT_FAILED: - log_error (_("waiting for process %d to terminate failed: %s\n"), - (int)pid, w32_strerror (-1)); - ec = GPG_ERR_GENERAL; - break; + case WAIT_TIMEOUT: + ec = GPG_ERR_TIMEOUT; + break; + + case WAIT_FAILED: + log_error (_("waiting for process %d to terminate failed: %s\n"), + (int)pid, w32_strerror (-1)); + ec = GPG_ERR_GENERAL; + break; - case WAIT_OBJECT_0: - if (!GetExitCodeProcess (proc, &exc)) - { - log_error (_("error getting exit code of process %d: %s\n"), - (int)pid, w32_strerror (-1) ); - ec = GPG_ERR_GENERAL; + case WAIT_OBJECT_0: + if (!GetExitCodeProcess (proc, &exc)) + { + log_error (_("error getting exit code of process %d: %s\n"), + (int)pid, w32_strerror (-1) ); + ec = GPG_ERR_GENERAL; } - else if (exc) - { - log_error (_("error running `%s': exit status %d\n"), + else if (exc) + { + log_error (_("error running `%s': exit status %d\n"), pgmname, (int)exc ); - if (exitcode) - *exitcode = (int)exc; - ec = GPG_ERR_GENERAL; - } - else - { - if (exitcode) - *exitcode = 0; - ec = 0; - } - CloseHandle (proc); - break; - - default: - log_error ("WaitForSingleObject returned unexpected " - "code %d for pid %d\n", code, (int)pid ); - ec = GPG_ERR_GENERAL; - break; + if (exitcode) + *exitcode = (int)exc; + ec = GPG_ERR_GENERAL; + } + else + { + if (exitcode) + *exitcode = 0; + ec = 0; + } + break; + + default: + log_error ("WaitForSingleObject returned unexpected " + "code %d for pid %d\n", code, (int)pid ); + ec = GPG_ERR_GENERAL; + break; } return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); } +void +gnupg_release_process (pid_t pid) +{ + if (pid != (pid_t)INVALID_HANDLE_VALUE) + { + HANDLE process = (HANDLE)pid; + + CloseHandle (process); + } +} + + /* Spawn a new process and immediatley detach from it. The name of the program to exec is PGMNAME and its arguments are in ARGV (the programname is automatically passed as first argument). diff --git a/common/exechelp.h b/common/exechelp.h index 56d0c1b9a..3a5b9e2b8 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -59,8 +59,8 @@ gpg_error_t gnupg_create_outbound_pipe (int filedes[2]); process are expected in the NULL terminated array ARGV. The program name itself should not be included there. If PREEXEC is not NULL, that function will be called right before the exec. - Calling gnupg_wait_process is required. Returns 0 on success or an - error code. + Calling gnupg_wait_process and gnupg_release_process is required. + Returns 0 on success or an error code. FLAGS is a bit vector: @@ -85,21 +85,41 @@ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], and ERRFD to stderr (any of them may be -1 to connect them to /dev/null). The arguments for the process are expected in the NULL terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process is required. Returns 0 - on success or an error code. */ + included there. Calling gnupg_wait_process and + gnupg_release_process is required. Returns 0 on success or an + error code. */ gpg_error_t gnupg_spawn_process_fd (const char *pgmname, const char *argv[], int infd, int outfd, int errfd, pid_t *pid); -/* Wait for the process identified by PID to terminate. PGMNAME should - be the same as supplied to the spawn fucntion and is only used for - diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL - for any failures of the spawned program or other error codes. If - EXITCODE is not NULL the exit code of the process is stored at this - address or -1 if it could not be retrieved. */ -gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode); +/* If HANG is true, waits for the process identified by PID to exit; + if HANG is false, checks whether the process has terminated. + PGMNAME should be the same as supplied to the spawn function and is + only used for diagnostics. Return values: + + 0 + The process exited successful. 0 is stored at R_EXITCODE. + + GPG_ERR_GENERAL + The process exited without success. The exit code of process + is then stored at R_EXITCODE. An exit code of -1 indicates + that the process terminated abnormally (e.g. due to a signal). + + GPG_ERR_TIMEOUT + The process is still running (returned only if HANG is false). + + GPG_ERR_INV_VALUE + An invalid PID has been specified. + + Other error codes may be returned as well. Unless otherwise noted, + -1 will be stored at R_EXITCODE. R_EXITCODE may be passed as NULL + if the exit code is not required (in that case an error messge will + be printed). Note that under Windows PID is not the process id but + the handle of the process. */ +gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang, + int *r_exitcode); /* Kill a process; that is send an appropriate signal to the process. @@ -107,6 +127,11 @@ gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode); from the system. An invalid PID is ignored. */ void gnupg_kill_process (pid_t pid); +/* Release the process identified by PID. This function is actually + only required for Windows but it does not harm to always call it. + It is a nop if PID is invalid. */ +void gnupg_release_process (pid_t pid); + /* Spawn a new process and immediatley detach from it. The name of the program to exec is PGMNAME and its arguments are in ARGV (the diff --git a/common/homedir.c b/common/homedir.c index a8bec42e4..3cd8e9dea 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -44,6 +44,24 @@ #include "sysutils.h" +#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 @@ -114,18 +132,7 @@ standard_homedir (void) /* Try to create the directory if it does not yet exists. */ if (access (dir, F_OK)) - { -#ifdef HAVE_W32CE_SYSTEM - wchar_t *wdir = utf8_to_wchar (dir); - if (wdir) - { - CreateDirectory (wdir, NULL); - xfree (wdir); - } -#else - CreateDirectory (dir, NULL); -#endif - } + w32_try_mkdir (dir); } else dir = GNUPG_DEFAULT_HOMEDIR; @@ -366,6 +373,54 @@ gnupg_localedir (void) } +/* 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) + { + 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\\dirmngr"; + } + return dir; +#else /*!HAVE_W32_SYSTEM*/ + return GNUPG_LOCALSTATEDIR "/cache/" PACKAGE_NAME; +#endif /*!HAVE_W32_SYSTEM*/ +} + + /* Return the default socket name used by DirMngr. */ const char * dirmngr_socket_name (void) @@ -379,7 +434,10 @@ dirmngr_socket_name (void) const char *s2; /* We need something akin CSIDL_COMMON_PROGRAMS, but local - (non-roaming). */ + (non-roaming). This is becuase the file needs to be on the + local machine and makes only sense on that machine. + CSIDL_WINDOWS seems to be the only location which guarantees + that. */ if (w32_shgetfolderpath (NULL, CSIDL_WINDOWS, NULL, 0, s1) < 0) strcpy (s1, "C:\\WINDOWS"); s2 = DIRSEP_S "S.dirmngr"; @@ -388,7 +446,7 @@ dirmngr_socket_name (void) } return name; #else /*!HAVE_W32_SYSTEM*/ - return "/var/run/dirmngr/socket"; + return GNUPG_LOCALSTATEDIR "/run/" PACKAGE_NAME "/S.dirmngr"; #endif /*!HAVE_W32_SYSTEM*/ } @@ -450,6 +508,13 @@ gnupg_module_name (int which) 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"); diff --git a/common/logging.c b/common/logging.c index f9ac69202..dbf9de4d9 100644 --- a/common/logging.c +++ b/common/logging.c @@ -63,7 +63,7 @@ static char prefix_buffer[80]; static int with_time; static int with_prefix; static int with_pid; -static unsigned long (*get_tid_callback)(void); +static int (*get_pid_suffix_cb)(unsigned long *r_value); static int running_detached; static int force_prefixes; @@ -336,9 +336,9 @@ log_set_fd (int fd) void -log_set_get_tid_callback (unsigned long (*cb)(void)) +log_set_pid_suffix_cb (int (*cb)(unsigned long *r_value)) { - get_tid_callback = cb; + get_pid_suffix_cb = cb; } @@ -441,9 +441,12 @@ do_logv (int level, int ignore_arg_ptr, const char *fmt, va_list arg_ptr) es_fputs_unlocked (prefix_buffer, logstream); if (with_pid || force_prefixes) { - if (get_tid_callback) - es_fprintf_unlocked (logstream, "[%u.%lx]", - (unsigned int)getpid (), get_tid_callback ()); + unsigned long pidsuf; + int pidfmt; + + if (get_pid_suffix_cb && (pidfmt=get_pid_suffix_cb (&pidsuf))) + es_fprintf_unlocked (logstream, pidfmt == 1? "[%u.%lu]":"[%u.%lx]", + (unsigned int)getpid (), pidsuf); else es_fprintf_unlocked (logstream, "[%u]", (unsigned int)getpid ()); } diff --git a/common/logging.h b/common/logging.h index 2c29a0b1b..91619179a 100644 --- a/common/logging.h +++ b/common/logging.h @@ -35,7 +35,7 @@ int log_get_errorcount (int clear); void log_inc_errorcount (void); void log_set_file( const char *name ); void log_set_fd (int fd); -void log_set_get_tid_callback (unsigned long (*cb)(void)); +void log_set_pid_suffix_cb (int (*cb)(unsigned long *r_value)); void log_set_prefix (const char *text, unsigned int flags); const char *log_get_prefix (unsigned int *flags); int log_test_fd (int fd); diff --git a/common/util.h b/common/util.h index 6fd874166..97ecef178 100644 --- a/common/util.h +++ b/common/util.h @@ -192,6 +192,7 @@ 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_socket_name (void); /* All module names. We also include gpg and gpgsm for the sake for @@ -206,6 +207,7 @@ const char *dirmngr_socket_name (void); #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); diff --git a/configure.ac b/configure.ac index 6ecfbfbce..c06defd77 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # configure.ac - for GnuPG 2.1 # Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, -# 2006, 2007, 2008, 2009 Free Software Foundation, Inc. +# 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc. # # This file is part of GnuPG. # @@ -81,11 +81,14 @@ disable_keyserver_path=no use_ccid_driver=yes use_standard_socket=no +try_ks_ldap=no + GNUPG_BUILD_PROGRAM(gpg, yes) GNUPG_BUILD_PROGRAM(gpgsm, yes) GNUPG_BUILD_PROGRAM(agent, yes) GNUPG_BUILD_PROGRAM(scdaemon, yes) GNUPG_BUILD_PROGRAM(g13, yes) +GNUPG_BUILD_PROGRAM(dirmngr, yes) GNUPG_BUILD_PROGRAM(tools, yes) GNUPG_BUILD_PROGRAM(doc, yes) GNUPG_BUILD_PROGRAM(symcryptrun, no) @@ -155,6 +158,15 @@ show_gnupg_protect_tool_pgm="(default)" test -n "$GNUPG_PROTECT_TOOL_PGM" \ && show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM" +AC_ARG_WITH(dirmngr-ldap-pgm, + [ --with-dirmngr-ldap-pgm=PATH Use PATH as the default for the dirmnge ldap wrapper)], + GNUPG_DIRMNGR_LDAP_PGM="$withval", GNUPG_DIRMNGR_LDAP_PGM="" ) +AC_SUBST(GNUPG_DIRMNGR_LDAP_PGM) +AM_CONDITIONAL(GNUPG_DIRMNGR_LDAP_PGM, test -n "$GNUPG_DIRMNGR_LDAP_PGM") +show_gnupg_dirmngr_ldap_pgm="(default)" +test -n "$GNUPG_DIRMNGR_LDAP_PGM" \ + && show_gnupg_dirmngr_ldap_pgm="$GNUPG_DIRMNGR_LDAP_PGM" + # Some folks want to use only the agent from this packet. Make it # easier for them by providing the configure option @@ -239,8 +251,8 @@ if test "$use_exec" = yes ; then AC_MSG_CHECKING([whether LDAP keyserver support is requested]) AC_ARG_ENABLE(ldap, AC_HELP_STRING([--disable-ldap],[disable LDAP keyserver interface only]), - try_ldap=$enableval, try_ldap=yes) - AC_MSG_RESULT($try_ldap) + try_ks_ldap=$enableval, try_ks_ldap=yes) + AC_MSG_RESULT($try_ks_ldap) AC_MSG_CHECKING([whether HKP keyserver support is requested]) AC_ARG_ENABLE(hkp, @@ -528,6 +540,7 @@ have_dosish_system=no have_w32_system=no have_w32ce_system=no use_simple_gettext=no +mmap_needed=yes case "${host}" in *-mingw32*) # special stuff for Windoze NT @@ -552,6 +565,7 @@ case "${host}" in esac try_gettext="no" use_simple_gettext=yes + mmap_needed=no ;; i?86-emx-os2 | i?86-*-os2*emx ) # OS/2 with the EMX environment @@ -738,6 +752,10 @@ AC_PATH_PROG(FUSERMOUNT, fusermount, /usr/bin/fusermount) AC_DEFINE_UNQUOTED(FUSERMOUNT, "${FUSERMOUNT}", [defines the filename of the fusermount program]) + +# Checks for dirmngr + + # # Checks for symcryptrun: # @@ -943,7 +961,7 @@ AM_CONDITIONAL(USE_DNS_SRV, test x"$use_dns_srv" = xyes) # # Check for LDAP # -if test "$try_ldap" = yes ; then +if test "$try_ks_ldap" = yes || test "$build_dirmngr" = "yes" ; then GNUPG_CHECK_LDAP($NETLIBS) fi @@ -1152,9 +1170,9 @@ AC_CHECK_DECLS(getpagesize) AC_FUNC_FSEEKO AC_FUNC_VPRINTF AC_FUNC_FORK -AC_CHECK_FUNCS([strerror strlwr tcgetattr mmap]) -AC_CHECK_FUNCS([strcasecmp strncasecmp ctermid times gmtime_r]) -AC_CHECK_FUNCS([unsetenv fcntl ftruncate]) +AC_CHECK_FUNCS([strerror strlwr tcgetattr mmap canonicalize_file_name]) +AC_CHECK_FUNCS([strcasecmp strncasecmp ctermid times gmtime_r strtoull]) +AC_CHECK_FUNCS([unsetenv fcntl ftruncate canonicalize_file_name]) AC_CHECK_FUNCS([gettimeofday getrusage getrlimit setrlimit clock_gettime]) AC_CHECK_FUNCS([atexit raise getpagesize strftime nl_langinfo setlocale]) AC_CHECK_FUNCS([waitpid wait4 sigaction sigprocmask pipe getaddrinfo]) @@ -1162,6 +1180,11 @@ AC_CHECK_FUNCS([ttyname rand ftello fsync stat lstat]) AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include ]) +# Dirmngr requires mmap on Unix systems. +if test $ac_cv_func_mmap != yes -a $mmap_needed = yes; then + AC_MSG_ERROR([[Sorry, the current implemenation requires mmap.]]) +fi + # # These are needed by the jnlib parts in common. # Note: We already checked pwd.h. @@ -1170,6 +1193,9 @@ AC_CHECK_FUNCS([memicmp stpcpy strsep strlwr strtoul memmove stricmp strtol \ memrchr isascii timegm getrusage setrlimit stat setlocale \ flockfile funlockfile fopencookie funopen getpwnam getpwuid \ getenv ]) +# end jnlib checks. + + # # gnulib checks @@ -1466,18 +1492,19 @@ if test "$build_agent_only" = "yes" ; then fi -AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") -AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") -AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") -AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") -AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes") -AM_CONDITIONAL(BUILD_TOOLS, test "$build_tools" = "yes") -AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") +AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes") +AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes") +AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes") +AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes") +AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes") +AM_CONDITIONAL(BUILD_DIRMNGR, test "$build_dirmngr" = "yes") +AM_CONDITIONAL(BUILD_TOOLS, test "$build_tools" = "yes") +AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes") AM_CONDITIONAL(BUILD_SYMCRYPTRUN, test "$build_symcryptrun" = "yes") -AM_CONDITIONAL(BUILD_GPGTAR,test "$build_gpgtar" = "yes") +AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes") AM_CONDITIONAL(RUN_GPG_TESTS, - test x$cross_compiling = xno -a "$build_gpg" = yes ) + test x$cross_compiling = xno -a "$build_gpg" = yes ) # @@ -1524,6 +1551,16 @@ if test "$have_ksba" = "no"; then *** (at least version $NEED_KSBA_VERSION using API $NEED_KSBA_API is required). ***]]) fi +if test "$gnupg_have_ldap" = "no"; then + die=yes + AC_MSG_NOTICE([[ +*** +*** You need a LDAP library to build this program. +*** Check out +*** http://www.openldap.org +*** for a suitable implementation. +***]]) +fi if test "$missing_pth" = "yes"; then AC_MSG_NOTICE([[ *** @@ -1562,6 +1599,7 @@ sm/Makefile agent/Makefile scd/Makefile g13/Makefile +dirmngr/Makefile keyserver/Makefile keyserver/gpg2keys_mailto keyserver/gpg2keys_test @@ -1585,9 +1623,11 @@ echo " Agent: $build_agent $build_agent_threaded Smartcard: $build_scdaemon $build_scdaemon_extra G13: $build_g13 + Dirmngr: $build_dirmngr Gpgtar: $build_gpgtar Protect tool: $show_gnupg_protect_tool_pgm + LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog new file mode 100644 index 000000000..b5294642f --- /dev/null +++ b/dirmngr/ChangeLog @@ -0,0 +1,1345 @@ +2010-06-09 Werner Koch + + * i18n.h: Remove. + + * Makefile.am (no-libgcrypt.c): New rule. + + * exechelp.h: Remove. + * exechelp.c: Remove. + (dirmngr_release_process): Change callers to use the gnupg func. + (dirmngr_wait_process): Likewise. + (dirmngr_kill_process): Likewise. This actually implements it for + W32. + * ldap.c (ldap_wrapper): s/get_dirmngr_ldap_path/gnupg_module_name/. + (ldap_wrapper_thread): Use gnupg_wait_process and adjust for + changed semantics. + (ldap_wrapper): Replace xcalloc by xtrycalloc. Replace spawn + mechanism. + + * server.c (start_command_handler): Remove assuan_set_log_stream. + + * validate.c: Remove gcrypt.h and ksba.h. + + * ldapserver.c: s/util.h/dirmngr.h/. + + * dirmngr.c (sleep) [W32]: Remove macro. + (main): s/sleep/gnupg_sleep/. + (pid_suffix_callback): Change arg type. + (my_gcry_logger): Remove. + (fixed_gcry_pth_init): New. + (main): Use it. + (FD2INT): Remove. + +2010-06-08 Werner Koch + + * misc.h (copy_time): Remove and replace by gnupg_copy_time which + allows to set a null date. + * misc.c (dump_isotime, get_time, get_isotime, set_time) + (check_isotime, add_isotime): Remove and replace all calls by the + versions from common/gettime.c. + + * crlcache.c, misc.c, misc.h: s/dirmngr_isotime_t/gnupg_isotime_t/. + * server.c, ldap.c: Reorder include directives. + * crlcache.h, misc.h: Remove all include directives. + + * certcache.c (cmp_simple_canon_sexp): Remove. + (compare_serialno): Rewrite using cmp_simple_canon_sexp from + common/sexputil.c + + * error.h: Remove. + + * dirmngr.c: Remove transitional option "--ignore-ocsp-servic-url". + (opts): Use ARGPARSE macros. + (i18n_init): Remove. + (main): Use GnuPG init functions. + + * dirmngr.h: Remove duplicated stuff now taken from ../common. + + * get-path.c, util.h: Remove. + + * Makefile.am: Adjust to GnuPG system. + * estream.c, estream.h, estream-printf.c, estream-printf.h: Remove. + +2010-06-07 Werner Koch + + * OAUTHORS, ONEWS, ChangeLog.1: New. + + * ChangeLog, Makefile.am, b64dec.c, b64enc.c, cdb.h, cdblib.c + * certcache.c, certcache.h, crlcache.c, crlcache.h, crlfetch.c + * crlfetch.h, dirmngr-client.c, dirmngr.c, dirmngr.h + * dirmngr_ldap.c, error.h, estream-printf.c, estream-printf.h + * estream.c, estream.h, exechelp.c, exechelp.h, get-path.c, http.c + * http.h, i18n.h, ldap-url.c, ldap-url.h, ldap.c, ldapserver.c + * ldapserver.h, misc.c, misc.h, ocsp.c, ocsp.h, server.c, util.h + * validate.c, validate.h: Imported from the current SVN of the + dirmngr package (only src/). + +2010-03-13 Werner Koch + + * dirmngr.c (int_and_ptr_u): New. + (pid_suffix_callback): Trick out compiler. + (start_connection_thread): Ditto. + (handle_connections): Ditto. + +2010-03-09 Werner Koch + + * dirmngr.c (set_debug): Allow numerical values. + +2009-12-15 Werner Koch + + * dirmngr.c: Add option --ignore-cert-extension. + (parse_rereadable_options): Implement. + * dirmngr.h (opt): Add IGNORED_CERT_EXTENSIONS. + * validate.c (unknown_criticals): Handle ignored extensions. + +2009-12-08 Marcus Brinkmann + + * dirmngr-client.c (start_dirmngr): Convert posix FDs to assuan fds. + +2009-11-25 Marcus Brinkmann + + * server.c (start_command_handler): Use assuan_fd_t and + assuan_fdopen on fds. + +2009-11-05 Marcus Brinkmann + + * server.c (start_command_handler): Update use of + assuan_init_socket_server. + * dirmngr-client.c (start_dirmngr): Update use of + assuan_pipe_connect and assuan_socket_connect. + +2009-11-04 Werner Koch + + * server.c (register_commands): Add help arg to + assuan_register_command. Change all command comments to strings. + +2009-11-02 Marcus Brinkmann + + * server.c (reset_notify): Take LINE argument, return gpg_error_t. + +2009-10-16 Marcus Brinkmann + + * Makefile.am: (dirmngr_LDADD): Link to $(LIBASSUAN_LIBS) instead + of $(LIBASSUAN_PTH_LIBS). + * dirmngr.c: Invoke ASSUAN_SYSTEM_PTH_IMPL. + (main): Call assuan_set_system_hooks and assuan_sock_init. + +2009-09-22 Marcus Brinkmann + + * dirmngr.c (main): Update to new Assuan interface. + * server.c (option_handler, cmd_ldapserver, cmd_isvalid) + (cmd_checkcrl, cmd_checkocsp, cmd_lookup, cmd_loadcrl) + (cmd_listcrls, cmd_cachecert, cmd_validate): Return gpg_error_t + instead int. + (register_commands): Likewise for member HANDLER. + (start_command_handler): Allocate context with assuan_new before + starting server. Release on error. + * dirmngr-client.c (main): Update to new Assuan interface. + (start_dirmngr): Allocate context with assuan_new before + connecting to server. Release on error. + +2009-08-12 Werner Koch + + * dirmngr-client.c (squid_loop_body): Flush stdout. Suggested by + Philip Shin. + +2009-08-07 Werner Koch + + * crlfetch.c (my_es_read): Add explicit check for EOF. + + * http.c (struct http_context_s): Turn IN_DATA and IS_HTTP_0_9 to + bit fields. + (struct cookie_s): Add CONTENT_LENGTH_VALID and CONTENT_LENGTH. + (parse_response): Parse the Content-Length header. + (cookie_read): Handle content length. + (http_open): Make NEED_HEADER the semi-default. + + * http.h (HTTP_FLAG_IGNORE_CL): New. + +2009-08-04 Werner Koch + + * ldap.c (ldap_wrapper_thread): Factor some code out to ... + (read_log_data): ... new. Close the log fd on error. + (ldap_wrapper_thread): Delay cleanup until the log fd is closed. + (SAFE_PTH_CLOSE): New. Use it instead of pth_close. + +2009-07-31 Werner Koch + + * server.c (cmd_loadcrl): Add option --url. + * dirmngr-client.c (do_loadcrl): Make use of --url. + + * crlfetch.c (crl_fetch): Remove HTTP_FLAG_NO_SHUTDOWN. Add + flag HTTP_FLAG_LOG_RESP with active DBG_LOOKUP. + + * http.c: Require estream. Remove P_ES macro. + (write_server): Remove. + (my_read_line): Remove. Replace all callers by es_read_line. + (send_request): Use es_asprintf. Always store the cookie. + (http_wait_response): Remove the need to dup the socket. USe new + shutdown flag. + * http.h (HTTP_FLAG_NO_SHUTDOWN): Rename to HTTP_FLAG_SHUTDOWN. + + * estream.c, estream.h, estream-printf.c, estream-printf.h: Update + from current libestream. This is provide es_asprintf. + +2009-07-20 Werner Koch + + * dirmngr.c (pid_suffix_callback): New. + (main): Use log_set_pid_suffix_cb. + (start_connection_thread): Put the fd into the tls. + + * ldap.c (ldap_wrapper_thread): Print ldap worker stati. + (ldap_wrapper_release_context): Print a debug info. + (end_cert_fetch_ldap): Release the reader. Might fix bug#999. + +2009-06-17 Werner Koch + + * util.h: Remove unused dotlock.h. + +2009-05-26 Werner Koch + + * ldap.c (ldap_wrapper): Show reader object in diagnostics. + * crlcache.c (crl_cache_reload_crl): Ditto. Change debug messages + to regular diagnostics. + * dirmngr_ldap.c (print_ldap_entries): Add extra diagnostics. + +2009-04-03 Werner Koch + + * dirmngr.h (struct server_local_s): Move back to ... + * server.c (struct server_local_s): ... here. + (get_ldapservers_from_ctrl): New. + * ldapserver.h (ldapserver_iter_begin): Use it. + +2008-10-29 Marcus Brinkmann + + * estream.c (es_getline): Add explicit cast to silence gcc -W + warning. + * crlcache.c (finish_sig_check): Likewise. + + * dirmngr.c (opts): Add missing initializer to silence gcc + -W warning. + * server.c (register_commands): Likewise. + * dirmngr-client.c (opts): Likewise. + * dirmngr_ldap.c (opts): Likewise. + + * dirmngr-client.c (status_cb, inq_cert, data_cb): Change return + type to gpg_error_t to silence gcc warning. + +2008-10-21 Werner Koch + + * certcache.c (load_certs_from_dir): Accept ".der" files. + + * server.c (get_istrusted_from_client): New. + * validate.c (validate_cert_chain): Add new optional arg + R_TRUST_ANCHOR. Adjust all callers + * crlcache.c (crl_cache_entry_s): Add fields USER_TRUST_REQ + and CHECK_TRUST_ANCHOR. + (release_one_cache_entry): Release CHECK_TRUST_ANCHOR. + (list_one_crl_entry): Print info about the new fields. + (open_dir, write_dir_line_crl): Support the new U-flag. + (crl_parse_insert): Add arg R_TRUST_ANCHOR and set it accordingly. + (crl_cache_insert): Store trust anchor in entry object. + (cache_isvalid): Ask client for trust is needed. + + * crlcache.c (open_dir): Replace xcalloc by xtrycalloc. + (next_line_from_file): Ditt. Add arg to return the gpg error. + Change all callers. + (update_dir): Replace sprintf and malloc by estream_asprintf. + (crl_cache_insert): Ditto. + (crl_cache_isvalid): Replace xmalloc by xtrymalloc. + (get_auth_key_id): Ditto. + (crl_cache_insert): Ditto. + + * crlcache.c (start_sig_check): Remove HAVE_GCRY_MD_DEBUG test. + * validate.c (check_cert_sig): Ditto. Remove workaround for bug + in libgcrypt 1.2. + + * estream.c, estream.h, estream-printf.c, estream-printf.h: Update + from current libestream (svn rev 61). + +2008-09-30 Marcus Brinkmann + + * get-path.c (get_dirmngr_ldap_path): Revert last change. + Instead, use dirmngr_libexecdir(). + (find_program_at_standard_place): Don't define for now. + +2008-09-30 Marcus Brinkmann + + * get-path.c (dirmngr_cachedir): Make COMP a pointer to const to + silence gcc warning. + (get_dirmngr_ldap_path): Look for dirmngr_ldap in the installation + directory. + +2008-08-06 Marcus Brinkmann + + * dirmngr.c (main): Mark the ldapserverlist-file option as + read-only. + +2008-07-31 Werner Koch + + * crlcache.c (start_sig_check) [!HAVE_GCRY_MD_DEBUG]: Use + gcry_md_start_debug + +2008-06-16 Werner Koch + + * get-path.c (w32_commondir): New. + (dirmngr_sysconfdir): Use it here. + (dirmngr_datadir): Ditto. + +2008-06-12 Marcus Brinkmann + + * Makefile.am (dirmngr_SOURCES): Add ldapserver.h and ldapserver.c. + * ldapserver.h, ldapserver.c: New files. + * ldap.c: Include "ldapserver.h". + (url_fetch_ldap): Use iterator to get session servers as well. + (attr_fetch_ldap, start_default_fetch_ldap): Likewise. + * dirmngr.c: Include "ldapserver.h". + (free_ldapservers_list): Removed. Change callers to + ldapserver_list_free. + (parse_ldapserver_file): Use ldapserver_parse_one. + * server.c: Include "ldapserver.h". + (cmd_ldapserver): New command. + (register_commands): Add new command LDAPSERVER. + (reset_notify): New function. + (start_command_handler): Register reset notify handler. + Deallocate session server list. + (lookup_cert_by_pattern): Use iterator to get session servers as well. + (struct server_local_s): Move to ... + * dirmngr.h (struct server_local_s): ... here. Add new member + ldapservers. + +2008-06-10 Werner Koch + + Support PEM encoded CRLs. Fixes bug#927. + + * crlfetch.c (struct reader_cb_context_s): New. + (struct file_reader_map_s): Replace FP by new context. + (register_file_reader, get_file_reader): Adjust accordingly. + (my_es_read): Detect Base64 encoded CRL and decode if needed. + (crl_fetch): Pass new context to the callback. + (crl_close_reader): Cleanup the new context. + * b64dec.c: New. Taken from GnuPG. + * util.h (struct b64state): Add new fields STOP_SEEN and + INVALID_ENCODING. + +2008-05-26 Marcus Brinkmann + + * dirmngr.c (main) [HAVE_W32_SYSTEM]: Switch to system + configuration on gpgconf related commands, and make all options + unchangeable. + +2008-03-25 Marcus Brinkmann + + * dirmngr_ldap.c (print_ldap_entries): Add code alternative for + W32 console stdout (unused at this point). + +2008-03-21 Marcus Brinkmann + + * estream.c (ESTREAM_MUTEX_DESTROY): New macro. + (es_create, es_destroy): Use it. + +2008-02-21 Werner Koch + + * validate.c (check_cert_sig) [HAVE_GCRY_MD_DEBUG]: Use new debug + function if available. + + * crlcache.c (abort_sig_check): Mark unused arg. + + * exechelp.c (dirmngr_release_process) [!W32]: Mark unsed arg. + + * validate.c (is_root_cert): New. Taken from GnuPG. + (validate_cert_chain): Use it in place of the simple DN compare. + +2008-02-15 Marcus Brinkmann + + * dirmngr.c (main): Reinitialize assuan log stream if necessary. + + * crlcache.c (update_dir) [HAVE_W32_SYSTEM]: Remove destination + file before rename. + (crl_cache_insert) [HAVE_W32_SYSTEM]: Remove destination file + before rename. + +2008-02-14 Marcus Brinkmann + + * validate.c (check_cert_policy): Use ksba_free instead of xfree. + (validate_cert_chain): Likewise. Free SUBJECT on error. + (cert_usage_p): Likewise. + + * crlcache.c (finish_sig_check): Undo last change. + (finish_sig_check): Close md. + (abort_sig_check): New function. + (crl_parse_insert): Use abort_sig_check to clean up. + + * crlcache.c (crl_cache_insert): Clean up CDB on error. + +2008-02-13 Marcus Brinkmann + + * crlcache.c (finish_sig_check): Call gcry_md_stop_debug. + * exechelp.h (dirmngr_release_process): New prototype. + * exechelp.c (dirmngr_release_process): New function. + * ldap.c (ldap_wrapper_thread): Release pid. + (destroy_wrapper): Likewise. + + * dirmngr.c (launch_reaper_thread): Destroy tattr. + (handle_connections): Likewise. + +2008-02-12 Marcus Brinkmann + + * ldap.c (pth_close) [! HAVE_W32_SYSTEM]: New macro. + (struct wrapper_context_s): New member log_ev. + (destroy_wrapper): Check FDs for != -1 rather than != 0. Use + pth_close instead of close. Free CTX->log_ev. + (ldap_wrapper_thread): Rewritten to use pth_wait instead of + select. Also use pth_read instead of read and pth_close instead + of close. + (ldap_wrapper): Initialize CTX->log_ev. + (reader_callback): Use pth_close instead of close. + * exechelp.c (create_inheritable_pipe) [HAVE_W32_SYSTEM]: Removed. + (dirmngr_spawn_process) [HAVE_W32_SYSTEM]: Use pth_pipe instead. + * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Include . + (main) [HAVE_W32_SYSTEM]: Set mode of stdout to binary. + +2008-02-01 Werner Koch + + * ldap.c: Remove all ldap headers as they are unused. + + * dirmngr_ldap.c (LDAP_DEPRECATED): New, to have OpenLDAP use the + old standard API. + +2008-01-10 Werner Koch + + * dirmngr-client.c: New option --local. + (do_lookup): Use it. + + * server.c (lookup_cert_by_pattern): Implement local lookup. + (return_one_cert): New. + * certcache.c (hexsn_to_sexp): New. + (classify_pattern, get_certs_bypattern): New. + + * misc.c (unhexify): Allow passing NULL for RESULT. + (cert_log_subject): Do not call ksba_free on an unused variable. + +2008-01-02 Marcus Brinkmann + + * Makefile.am (dirmngr_LDADD, dirmngr_ldap_LDADD) + (dirmngr_client_LDADD): Add $(LIBICONV). Reported by Michael + Nottebrock. + +2007-12-11 Werner Koch + + * server.c (option_handler): New option audit-events. + * dirmngr.h (struct server_control_s): Add member AUDIT_EVENTS. + +2007-11-26 Marcus Brinkmann + + * get-path.c (dirmngr_cachedir): Create intermediate directories. + (default_socket_name): Use CSIDL_WINDOWS. + +2007-11-21 Werner Koch + + * server.c (lookup_cert_by_pattern): Add args SINGLE and CACHE_ONLY. + (cmd_lookup): Add options --single and --cache-only. + +2007-11-16 Werner Koch + + * certcache.c (load_certs_from_dir): Also log the subject DN. + * misc.c (cert_log_subject): New. + +2007-11-14 Werner Koch + + * dirmngr-client.c: Replace --lookup-url by --url. + (main): Remove extra code for --lookup-url. + (do_lookup): Remove LOOKUP_URL arg and use the + global option OPT.URL. + + * server.c (has_leading_option): New. + (cmd_lookup): Use it. + + * crlfetch.c (fetch_cert_by_url): Use GPG_ERR_INV_CERT_OBJ. + (fetch_cert_by_url): Use gpg_error_from_syserror. + +2007-11-14 Moritz (wk) + + * dirmngr-client.c: New command: --lookup-url . + (do_lookup): New parameter: lookup_url. If TRUE, include "--url" + switch in LOOKUP transaction. + (enum): New entry: oLookupUrl. + (opts): Likewise. + (main): Handle oLookupUrl. New variable: cmd_lookup_url, set + during option parsing, pass to do_lookup() and substitute some + occurences of "cmd_lookup" with "cmd_lookup OR cmd_lookup_url". + * crlfetch.c (fetch_cert_by_url): New function, uses + url_fetch_ldap() to create a reader object and libksba functions + to read a single cert from that reader. + * server.c (lookup_cert_by_url, lookup_cert_by_pattern): New + functions. + (cmd_lookup): Moved almost complete code ... + (lookup_cert_by_pattern): ... here. + (cmd_lookup): Support new optional argument: --url. Depending on + the presence of that switch, call lookup_cert_by_url() or + lookup_cert_by_pattern(). + (lookup_cert_by_url): Heavily stripped down version of + lookup_cert_by_pattern(), using fetch_cert_by_url. + +2007-10-24 Marcus Brinkmann + + * exechelp.c (dirmngr_spawn_process): Fix child handles. + +2007-10-05 Marcus Brinkmann + + * dirmngr.h: Include assuan.h. + (start_command_handler): Change type of FD to assuan_fd_t. + * dirmngr.c: Do not include w32-afunix.h. + (socket_nonce): New global variable. + (create_server_socket): Use assuan socket wrappers. Remove W32 + specific stuff. Save the server nonce. + (check_nonce): New function. + (start_connection_thread): Call it. + (handle_connections): Change args to assuan_fd_t. + * server.c (start_command_handler): Change type of FD to assuan_fd_t. + +2007-09-12 Marcus Brinkmann + + * dirmngr.c (main): Percent escape pathnames in --gpgconf-list output. + +2007-08-27 Moritz Schulte + + * src/Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SOCKETDIR based on + $(localstatedir). + * src/get-path.c (default_socket_name): Use DIRMNGR_SOCKETDIR + instead of hard-coded "/var/run/dirmngr". + +2007-08-16 Werner Koch + + * get-path.c (get_dirmngr_ldap_path): Make PATHNAME const. + + * dirmngr.c (my_ksba_hash_buffer): Mark unused arg. + (dirmngr_init_default_ctrl): Ditto. + (my_gcry_logger): Ditto. + * dirmngr-client.c (status_cb): Ditto. + * dirmngr_ldap.c (catch_alarm): Ditto. + * estream-printf.c (pr_bytes_so_far): Ditto. + * estream.c (es_func_fd_create): Ditto. + (es_func_fp_create): Ditto. + (es_write_hexstring): Ditto. + * server.c (cmd_listcrls): Ditto. + (cmd_cachecert): Ditto. + * crlcache.c (cache_isvalid): Ditto. + * ocsp.c (do_ocsp_request): Ditto. + * ldap.c (ldap_wrapper_thread): Ditto. + * http.c (http_register_tls_callback): Ditto. + (connect_server): Ditto. + (write_server) [!HTTP_USE_ESTREAM]: Don't build. + +2007-08-14 Werner Koch + + * get-path.c (dirmngr_cachedir) [W32]: Use CSIDL_LOCAL_APPDATA. + +2007-08-13 Werner Koch + + * dirmngr.c (handle_connections): Use a timeout in the accept + function. Block signals while creating a new thread. + (shutdown_pending): Needs to be volatile as also accessed bt the + service function. + (w32_service_control): Do not use the regular log fucntions here. + (handle_tick): New. + (main): With system_service in effect use aDaemon as default + command. + (main) [W32]: Only temporary redefine main for the sake of Emacs's + "C-x 4 a". + + * dirmngr-client.c (main) [W32]: Initialize sockets. + (start_dirmngr): Use default_socket_name instead of a constant. + * Makefile.am (dirmngr_client_SOURCES): Add get-path.c + +2007-08-09 Werner Koch + + * dirmngr.c (parse_ocsp_signer): New. + (parse_rereadable_options): Set opt.ocsp_signer to this. + * dirmngr.h (fingerprint_list_t): New. + * ocsp.c (ocsp_isvalid, check_signature, validate_responder_cert): + Allow for several default ocscp signers. + (ocsp_isvalid): Return GPG_ERR_NO_DATA for an unknwon status. + + * dirmngr-client.c: New option --force-default-responder. + + * server.c (has_option, skip_options): New. + (cmd_checkocsp): Add option --force-default-responder. + (cmd_isvalid): Ditto. Also add option --only-ocsp. + + * ocsp.c (ocsp_isvalid): New arg FORCE_DEFAULT_RESPONDER. + + * dirmngr.c: New option --ocsp-max-period. + * ocsp.c (ocsp_isvalid): Implement it and take care that a missing + next_update is to be ignored. + + * crlfetch.c (my_es_read): New. Use it instead of es_read. + + * estream.h, estream.c, estream-printf.c: Updated from current + libestream SVN. + +2007-08-08 Werner Koch + + * crlcache.c (crl_parse_insert): Hack to allow for a missing + nextUpdate. + + * dirmngr_ldap.c (print_ldap_entries): Strip the extension from + the want_attr. + + * exechelp.c (dirmngr_wait_process): Reworked for clear error + semantics. + * ldap.c (ldap_wrapper_thread): Adjust for new + dirmngr_wait_process semantics. + +2007-08-07 Werner Koch + + * get-path.c (default_socket_name) [!W32]: Fixed syntax error. + + * ldap.c (X509CACERT, make_url, fetch_next_cert_ldap): Support + x509caCert as used by the Bundesnetzagentur. + (ldap_wrapper): Do not pass the prgtram name as the first + argument. dirmngr_spawn_process takes care of that. + +2007-08-04 Marcus Brinkmann + + * dirmngr.h (opt): Add member system_service. + * dirmngr.c (opts) [HAVE_W32_SYSTEM]: New entry for option + --service. + (DEFAULT_SOCKET_NAME): Removed. + (service_handle, service_status, + w32_service_control) [HAVE_W32_SYSTEM]: New symbols. + (main) [HAVE_W32_SYSTEM]: New entry point for --service. Rename + old function to ... + (real_main) [HAVE_W32_SYSTEM]: ... this. Use default_socket_name + instead of DEFAULT_SOCKET_NAME, and similar for other paths. + Allow colons in Windows socket path name, and implement --service + option. + * util.h (dirmngr_sysconfdir, dirmngr_libexecdir, dirmngr_datadir, + dirmngr_cachedir, default_socket_name): New prototypes. + * get-path.c (dirmngr_sysconfdir, dirmngr_libexecdir) + (dirmngr_datadir, dirmngr_cachedir, default_socket_name): New + functions. + (DIRSEP_C, DIRSEP_S): New macros. + +2007-08-03 Marcus Brinkmann + + * get-path.c: Really add the file this time. + +2007-07-31 Marcus Brinkmann + + * crlfetch.c: Include "estream.h". + (crl_fetch): Use es_read callback instead a file handle. + (crl_close_reader): Use es_fclose instead of fclose. + (struct file_reader_map_s): Change type of FP to estream_t. + (register_file_reader, crl_fetch, crl_close_reader): Likewise. + * ocsp.c: Include "estream.h". + (read_response): Change type of FP to estream_t. + (read_response, do_ocsp_request): Use es_* variants of I/O + functions. + + * http.c: Include . + (http_wait_response) [HAVE_W32_SYSTEM]: Use DuplicateHandle. + (cookie_read): Use pth_read instead read. + (cookie_write): Use pth_write instead write. + +2007-07-30 Marcus Brinkmann + + * ldap-url.c (ldap_str2charray): Fix buglet in ldap_utf8_strchr + invocation. + +2007-07-27 Marcus Brinkmann + + * estream.h, estream.c: Update from recent GnuPG. + + * get-path.c: New file. + * Makefile.am (dirmngr_SOURCES): Add get-path.c. + * util.h (default_homedir, get_dirmngr_ldap_path): New prototypes. + * dirmngr.c (main): Use default_homedir(). + * ldap-url.h: Remove japanese white space (sorry!). + +2007-07-26 Marcus Brinkmann + + * ldap.c (pth_yield): Remove macro. + + * ldap.c (pth_yield) [HAVE_W32_SYSTEM]: Define to Sleep(0). + + * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Do not include , but + , and "ldap-url.h". + * ldap.c [HAVE_W32_SYSTEM]: Do not include , but + and . + + * ldap-url.c: Do not include , but , + and "ldap-url.h". + (LDAP_P): New macro. + * ldap-url.h: New file. + * Makefile.am (ldap_url): Add ldap-url.h. + + * Makefile.am (ldap_url): New variable. + (dirmngr_ldap_SOURCES): Add $(ldap_url). + (dirmngr_ldap_LDADD): Add $(LIBOBJS). + * ldap-url.c: New file, excerpted from OpenLDAP. + * dirmngr.c (main) [HAVE_W32_SYSTEM]: Avoid the daemonization. + * dirmngr_ldap.c: Include "util.h". + (main) [HAVE_W32_SYSTEM]: Don't set up alarm. + (set_timeout) [HAVE_W32_SYSTEM]: Likewise. + * ldap.c [HAVE_W32_SYSTEM]: Add macros for setenv and pth_yield. + * no-libgcrypt.h (NO_LIBGCRYPT): Define. + * util.h [NO_LIBGCRYPT]: Don't include . + +2007-07-23 Marcus Brinkmann + + * Makefile.am (dirmngr_SOURCES): Add exechelp.h and exechelp.c. + * exechelp.h, exechelp.c: New files. + * ldap.c: Don't include but "exechelp.h". + (destroy_wrapper, ldap_wrapper_thread, + ldap_wrapper_connection_cleanup): Use dirmngr_kill_process instead + of kill. + (ldap_wrapper_thread): Use dirmngr_wait_process instead of + waitpid. + (ldap_wrapper): Use dirmngr_spawn_process. + +2007-07-20 Marcus Brinkmann + + * certcache.c (cert_cache_lock): Do not initialize statically. + (init_cache_lock): New function. + (cert_cache_init): Call init_cache_lock. + + * estream.h, estream.c, estream-printf.h, estream-printf.c: New + files. + * Makefile.am (dirmngr_SOURCES): Add estream.c, estream.h, + estream-printf.c, estream-printf.h. + + * http.c: Update to latest version from GnuPG. + + * Makefile.am (cdb_sources) + * cdblib.c: Port to windows (backport from tinycdb 0.76). + + * crlcache.c [HAVE_W32_SYSTEM]: Don't include sys/utsname.h. + [MKDIR_TAKES_ONE_ARG]: Define mkdir as a macro for such systems. + (update_dir, crl_cache_insert) [HAVE_W32_SYSTEM]: Don't get uname. + * server.c (start_command_handler) [HAVE_W32_SYSTEM]: Don't log + peer credentials. + + * dirmngr.c [HAVE_W32_SYSTEM]: Do not include sys/socket.h or + sys/un.h, but ../jnlib/w32-afunix.h. + (sleep) [HAVE_W32_SYSTEM]: New macro. + (main) [HAVE_W32_SYSTEM]: Don't mess with SIGPIPE. Use W32 socket + API. + (handle_signal) [HAVE_W32_SYSTEM]: Deactivate the bunch of the + code. + (handle_connections) [HAVE_W32_SYSTEM]: don't handle signals. + +2006-11-29 Werner Koch + + * dirmngr.c (my_strusage): Use macro for the bug report address + and the copyright line. + * dirmngr-client.c (my_strusage): Ditto. + * dirmngr_ldap.c (my_strusage): Ditto. + + * Makefile.am: Do not link against LIBICONV. + +2006-11-19 Werner Koch + + * dirmngr.c: Include i18n.h. + +2006-11-17 Werner Koch + + * Makefile.am (dirmngr_LDADD): Use LIBASSUAN_PTH_LIBS. + +2006-11-16 Werner Koch + + * server.c (start_command_handler): Replaced + assuan_init_connected_socket_server by assuan_init_socket_server_ext. + + * crlcache.c (update_dir): Put a diagnostic into DIR.txt. + (open_dir): Detect invalid and duplicate entries. + (update_dir): Fixed search for second field. + +2006-10-23 Werner Koch + + * dirmngr.c (main): New command --gpgconf-test. + +2006-09-14 Werner Koch + + * server.c (start_command_handler): In vebose mode print + information about the peer. This may later be used to restrict + certain commands. + +2006-09-12 Werner Koch + + * server.c (start_command_handler): Print a more informative hello + line. + * dirmngr.c: Moved config_filename into the opt struct. + +2006-09-11 Werner Koch + + Changed everything to use Assuan with gpg-error codes. + * maperror.c: Removed. + * server.c (map_to_assuan_status): Removed. + * dirmngr.c (main): Set assuan error source. + * dirmngr-client.c (main): Ditto. + +2006-09-04 Werner Koch + + * crlfetch.c (crl_fetch): Implement HTTP redirection. + * ocsp.c (do_ocsp_request): Ditto. + + New HTTP code version taken from gnupg svn release 4236. + * http.c (http_get_header): New. + (capitalize_header_name, store_header): New. + (parse_response): Store headers away. + (send_request): Return GPG_ERR_NOT_FOUND if connect_server failed. + * http.h: New flag HTTP_FLAG_NEED_HEADER. + +2006-09-01 Werner Koch + + * crlfetch.c (register_file_reader, get_file_reader): New. + (crl_fetch): Register the file pointer for HTTP. + (crl_close_reader): And release it. + + * http.c, http.h: Updated from GnuPG SVN trunk. Changed all users + to adopt the new API. + * dirmngr.h: Moved inclusion of jnlib header to ... + * util.h: .. here. This is required becuase http.c includes only + a file util.h but makes use of log_foo. Include gcrypt.h so that + gcry_malloc et al are declared. + +2006-08-31 Werner Koch + + * ocsp.c (check_signature): Make use of the responder id. + +2006-08-30 Werner Koch + + * validate.c (check_cert_sig): Workaround for rimemd160. + (allowed_ca): Always allow trusted CAs. + + * dirmngr.h (cert_ref_t): New. + (struct server_control_s): Add field OCSP_CERTS. + * server.c (start_command_handler): Release new field + * ocsp.c (release_ctrl_ocsp_certs): New. + (check_signature): Store certificates in OCSP_CERTS. + + * certcache.c (find_issuing_cert): Reset error if cert was found + by subject. + (put_cert): Add new arg FPR_BUFFER. Changed callers. + (cache_cert_silent): New. + + * dirmngr.c (parse_rereadable_options): New options + --ocsp-max-clock-skew and --ocsp-current-period. + * ocsp.c (ocsp_isvalid): Use them here. + + * ocsp.c (validate_responder_cert): New optional arg signer_cert. + (check_signature_core): Ditto. + (check_signature): Use the default signer certificate here. + +2006-06-27 Werner Koch + + * dirmngr-client.c (inq_cert): Take care of SENDCERT_SKI. + +2006-06-26 Werner Koch + + * crlcache.c (lock_db_file): Count open files when needed. + (find_entry): Fixed deleted case. + +2006-06-23 Werner Koch + + * misc.c (cert_log_name): New. + + * certcache.c (load_certs_from_dir): Also print certificate name. + (find_cert_bysn): Release ISSDN. + + * validate.h: New VALIDATE_MODE_CERT. + * server.c (cmd_validate): Use it here so that no policy checks + are done. Try to validated a cached copy of the target. + + * validate.c (validate_cert_chain): Implement a validation cache. + (check_revocations): Print more diagnostics. Actually use the + loop variable and not the head of the list. + (validate_cert_chain): Do not check revocations of CRL issuer + certificates in plain CRL check mode. + * ocsp.c (ocsp_isvalid): Make sure it is reset for a status of + revoked. + +2006-06-22 Werner Koch + + * validate.c (cert_use_crl_p): New. + (cert_usage_p): Add a mode 6 for CRL signing. + (validate_cert_chain): Check that the certificate may be used for + CRL signing. Print a note when not running as system daemon. + (validate_cert_chain): Reduce the maximum depth from 50 to 10. + + * certcache.c (find_cert_bysn): Minor restructuring + (find_cert_bysubject): Ditto. Use get_cert_local when called + without KEYID. + * crlcache.c (get_crlissuer_cert_bysn): Removed. + (get_crlissuer_cert): Removed. + (crl_parse_insert): Use find_cert_bysubject and find_cert_bysn + instead of the removed functions. + +2006-06-19 Werner Koch + + * certcache.c (compare_serialno): Silly me. Using 0 as true is + that hard; tsss. Fixed call cases except for the only working one + which are both numbers of the same length. + +2006-05-15 Werner Koch + + * crlfetch.c (crl_fetch): Use no-shutdown flag for HTTP. This + seems to be required for "IBM_HTTP_Server/2.0.47.1 Apache/2.0.47 + (Unix)". + + * http.c (parse_tuple): Set flag to to indicate no value. + (build_rel_path): Take care of it. + + * crlcache.c (crl_cache_reload_crl): Also iterate over all names + within a DP. + +2005-09-28 Marcus Brinkmann + + * Makefile.am (dirmngr_LDADD): Add @LIBINTL@ and @LIBICONV@. + (dirmngr_ldap_LDADD): Likewise. + (dirmngr_client_LDADD): Likewise. + +2005-09-12 Werner Koch + + * dirmngr.c: Fixed description to match the one in gpgconf. + +2005-06-15 Werner Koch + + * server.c (cmd_lookup): Take care of NO_DATA which might get + returned also by start_cert_fetch(). + +2005-04-20 Werner Koch + + * ldap.c (ldap_wrapper_wait_connections): Set a shutdown flag. + (ldap_wrapper_thread): Handle shutdown in a special way. + +2005-04-19 Werner Koch + + * server.c (get_cert_local, get_issuing_cert_local) + (get_cert_local_ski): Bail out if called without a local context. + +2005-04-18 Werner Koch + + * certcache.c (find_issuing_cert): Fixed last resort method which + should be finding by subject and not by issuer. Try to locate it + also using the keyIdentifier method. Improve error reporting. + (cmp_simple_canon_sexp): New. + (find_cert_bysubject): New. + (find_cert_bysn): Ask back to the caller before trying an extarnl + lookup. + * server.c (get_cert_local_ski): New. + * crlcache.c (crl_parse_insert): Also try to locate issuer + certificate using the keyIdentifier. Improved error reporting. + +2005-04-14 Werner Koch + + * ldap.c (start_cert_fetch_ldap): Really return ERR. + +2005-03-17 Werner Koch + + * http.c (parse_response): Changed MAXLEN and LEN to size_t to + match the requirement of read_line. + * http.h (http_context_s): Ditto for BUFFER_SIZE. + +2005-03-15 Werner Koch + + * ldap.c: Included time.h. Reported by Bernhard Herzog. + +2005-03-09 Werner Koch + + * dirmngr.c: Add a note to the help listing check the man page for + other options. + +2005-02-01 Werner Koch + + * crlcache.c (crl_parse_insert): Renamed a few variables and + changed diagnostic strings for clarity. + (get_issuer_cert): Renamed to get_crlissuer_cert. Try to locate + the certificate from the cache using the subject name. Use new + fetch function. + (get_crlissuer_cert_bysn): New. + (crl_parse_insert): Use it here. + * crlfetch.c (ca_cert_fetch): Changed interface. + (fetch_next_ksba_cert): New. + * ldap.c (run_ldap_wrapper): Add arg MULTI_MODE. Changed all + callers. + (start_default_fetch_ldap): New + * certcache.c (get_cert_bysubject): New. + (clean_cache_slot, put_cert): Store the subject DN if available. + (MAX_EXTRA_CACHED_CERTS): Increase limit of cachable certificates + to 1000. + (find_cert_bysn): Loop until a certificate with a matching S/N has + been found. + + * dirmngr.c (main): Add honor-http-proxy to the gpgconf list. + +2005-01-31 Werner Koch + + * ldap.c: Started to work on support for userSMIMECertificates. + + * dirmngr.c (main): Make sure to always pass a server control + structure to the caching functions. Reported by Neil Dunbar. + +2005-01-05 Werner Koch + + * dirmngr-client.c (read_pem_certificate): Skip trailing percent + escaped linefeeds. + +2005-01-03 Werner Koch + + * dirmngr-client.c (read_pem_certificate): New. + (read_certificate): Divert to it depending on pem option. + (squid_loop_body): New. + (main): New options --pem and --squid-mode. + +2004-12-17 Werner Koch + + * dirmngr.c (launch_ripper_thread): Renamed to launch_reaper_thread. + (shutdown_reaper): New. Use it for --server and --daemon. + * ldap.c (ldap_wrapper_wait_connections): New. + +2004-12-17 Werner Koch + + * Makefile.am (dirmngr_ldap_LDADD): Adjusted for new LDAP checks. + +2004-12-16 Werner Koch + + * ldap.c (ldap_wrapper): Peek on the output to detect empty output + early. + +2004-12-15 Werner Koch + + * ldap.c (ldap_wrapper): Print a diagnostic after forking for the + ldap wrapper. + * certcache.h (find_cert_bysn): Add this prototype. + * crlcache.c (start_sig_check): Write CRL hash debug file. + (finish_sig_check): Dump the signer's certificate. + (crl_parse_insert): Try to get the issuing cert by authKeyId. + Moved certificate retrieval after item processing. + +2004-12-13 Werner Koch + + * dirmngr_ldap.c (catch_alarm, set_timeout): new. + (main): Install alarm handler. Add new option --only-search-timeout. + (print_ldap_entries, fetch_ldap): Use set_timeout (); + * dirmngr.h: Make LDAPTIMEOUT a simple unsigned int. Change all + initializations. + * ldap.c (start_cert_fetch_ldap, run_ldap_wrapper): Pass timeout + option to the wrapper. + (INACTIVITY_TIMEOUT): Depend on LDAPTIMEOUT. + (run_ldap_wrapper): Add arg IGNORE_TIMEOUT. + (ldap_wrapper_thread): Check for special timeout exit code. + + * dirmngr.c: Workaround a typo in gpgconf for + ignore-ocsp-service-url. + +2004-12-10 Werner Koch + + * ldap.c (url_fetch_ldap): Use TMP and not a HOST which is always + NULL. + * misc.c (host_and_port_from_url): Fixed bad encoding detection. + +2004-12-03 Werner Koch + + * crlcache.c (crl_cache_load): Re-implement it. + + * dirmngr-client.c: New command --load-crl + (do_loadcrl): New. + + * dirmngr.c (parse_rereadable_options, main): Make --allow-ocsp, + --ocsp-responder, --ocsp-signer and --max-replies re-readable. + + * ocsp.c (check_signature): try to get the cert from the cache + first. + (ocsp_isvalid): Print the next and this update times on time + conflict. + + * certcache.c (load_certs_from_dir): Print the fingerprint for + trusted certificates. + (get_cert_byhexfpr): New. + * misc.c (get_fingerprint_hexstring_colon): New. + +2004-12-01 Werner Koch + + * Makefile.am (dirmngr_LDADD): Don't use LDAP_LIBS. + + * validate.c (validate_cert_chain): Fixed test; as written in the + comment we want to do this only in daemon mode. For clarity + reworked by using a linked list of certificates and include root + and tragte certificate. + (check_revocations): Likewise. Introduced a recursion sentinel. + +2004-11-30 Werner Koch + + * crlfetch.c (ca_cert_fetch, crl_fetch_default): Do not use the + binary prefix as this will be handled in the driver. + + * dirmngr_ldap.c: New option --log-with-pid. + (fetch_ldap): Handle LDAP_NO_SUCH_OBJECT. + * ldap.c (run_ldap_wrapper, start_cert_fetch_ldap): Use new log + option. + + +2004-11-25 Werner Koch + + * Makefile.am (dirmngr_ldap_CFLAGS): Added GPG_ERROR_CFLAGS. + Noted by Bernhard Herzog. + +2004-11-24 Werner Koch + + * ldap.c (ldap_wrapper): Fixed default name of the ldap wrapper. + + * b64enc.c (b64enc_start, b64enc_finish): Use standard strdup/free + to manage memory. + + * dirmngr.c: New options --ignore-http-dp, --ignore-ldap-dp and + --ignore-ocsp-service-url. + * crlcache.c (crl_cache_reload_crl): Implement them. + * ocsp.c (ocsp_isvalid): Ditto. + +2004-11-23 Werner Koch + + * ldap.c (ldap_wrapper_thread, reader_callback, ldap_wrapper): + Keep a timestamp and terminate the wrapper after some time of + inactivity. + + * dirmngr-client.c (do_lookup): New. + (main): New option --lookup. + (data_cb): New. + * b64enc.c: New. Taken from GnuPG 1.9. + * no-libgcrypt.c (gcry_strdup): Added. + + * ocsp.c (ocsp_isvalid): New arg CERT and lookup the issuer + certificate using the standard methods. + + * server.c (cmd_lookup): Truncation is now also an indication for + error. + (cmd_checkocsp): Implemented. + + * dirmngr_ldap.c (fetch_ldap): Write an error marker for a + truncated search. + * ldap.c (add_server_to_servers): Reactivated. + (url_fetch_ldap): Call it here and try all configured servers in + case of a a failed lookup. + (fetch_next_cert_ldap): Detect the truncation error flag. + * misc.c (host_and_port_from_url, remove_percent_escapes): New. + +2004-11-22 Werner Koch + + * dirmngr_ldap.c (main): New option --proxy. + * ocsp.c (do_ocsp_request): Take care of opt.disable_http. + * crlfetch.c (crl_fetch): Honor the --honor-http-proxy variable. + (crl_fetch): Take care of opt.disable_http and disable_ldap. + (crl_fetch_default, ca_cert_fetch, start_cert_fetch): + * ldap.c (run_ldap_wrapper): New arg PROXY. + (url_fetch_ldap, attr_fetch_ldap, start_cert_fetch_ldap): Pass it. + + * http.c (http_open_document): Add arg PROXY. + (http_open): Ditto. + (send_request): Ditto and implement it as an override. + + * ocsp.c (validate_responder_cert): Use validate_cert_chain. + + * Makefile.am (AM_CPPFLAGS): Add macros for a few system + directories. + * dirmngr.h (opt): New members homedir_data, homedir_cache, + ldap_wrapper_program, system_daemon, honor_http_proxy, http_proxy, + ldap_proxy, only_ldap_proxy, disable_ldap, disable_http. + * dirmngr.c (main): Initialize new opt members HOMEDIR_DATA and + HOMEDIR_CACHE. + (parse_rereadable_options): New options --ldap-wrapper-program, + --http-wrapper-program, --disable-ldap, --disable-http, + --honor-http-proxy, --http-proxy, --ldap-proxy, --only-ldap-proxy. + (reread_configuration): New. + + * ldap.c (ldap_wrapper): Use the correct name for the wrapper. + + * crlcache.c (DBDIR_D): Make it depend on opt.SYSTEM_DAEMON. + (cleanup_cache_dir, open_dir, update_dir, make_db_file_name) + (crl_cache_insert, create_directory_if_needed): Use opt.HOMEDIR_CACHE + + * validate.c (check_revocations): New. + * crlcache.c (crl_cache_isvalid): Factored most code out to + (cache_isvalid): .. new. + (crl_cache_cert_isvalid): New. + * server.c (cmd_checkcrl): Cleaned up by using this new function. + (reload_crl): Moved to .. + * crlcache.c (crl_cache_reload_crl): .. here and made global. + + * certcache.c (cert_compute_fpr): Renamed from computer_fpr and + made global. + (find_cert_bysn): Try to lookup missing certs. + (cert_cache_init): Intialize using opt.HOMEDIR_DATA. + + +2004-11-19 Werner Koch + + * dirmngr-client.c (status_cb): New. Use it in very verbose mode. + + * server.c (start_command_handler): Malloc the control structure + and properly release it. Removed the primary_connection + hack. Cleanup running wrappers. + (dirmngr_status): Return an error code. + (dirmngr_tick): Return an error code and detect a + cancellation. Use wall time and not CPU time. + * validate.c (validate_cert_chain): Add CTRL arg and changed callers. + * crlcache.c (crl_cache_isvalid): + * crlfetch.c (ca_cert_fetch, start_cert_fetch, crl_fetch_default) + (crl_fetch): Ditto. + * ldap.c (ldap_wrapper, run_ldap_wrapper, url_fetch_ldap) + (attr_fetch_ldap, start_cert_fetch_ldap): Ditto. + (ldap_wrapper_release_context): Reset the stored CTRL. + (reader_callback): Periodically call dirmngr_tick. + (ldap_wrapper_release_context): Print an error message for read + errors. + (ldap_wrapper_connection_cleanup): New. + +2004-11-18 Werner Koch + + * dirmngr.c (main): Do not cd / if not running detached. + + * dirmngr-client.c: New options --cache-cert and --validate. + (do_cache, do_validate): New. + * server.c (cmd_cachecert, cmd_validate): New. + + * crlcache.c (get_issuer_cert): Make use of the certificate cache. + (crl_parse_insert): Validate the issuer certificate. + + * dirmngr.c (handle_signal): Reinitialize the certificate cache on + a HUP. + (struct opts): Add --homedir to enable the already implemented code. + (handle_signal): Print stats on SIGUSR1. + + * certcache.c (clean_cache_slot, cert_cache_init) + (cert_cache_deinit): New. + (acquire_cache_read_lock, acquire_cache_write_lock) + (release_cache_lock): New. Use them where needed. + (put_cert): Renamed from put_loaded_cert. + (cache_cert): New. + (cert_cache_print_stats): New. + (compare_serialno): Fixed. + +2004-11-16 Werner Koch + + * Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SYSCONFDIR and + DIRMNGR_LIBEXECDIR. + + * misc.c (dump_isotime, dump_string, dump_cert): New. Taken from + gnupg 1.9. + (dump_serial): New. + +2004-11-15 Werner Koch + + * validate.c: New. Based on gnupg's certchain.c + + * ldap.c (get_cert_ldap): Removed. + (read_buffer): New. + (start_cert_fetch_ldap, fetch_next_cert_ldap) + (end_cert_fetch_ldap): Rewritten to make use of the ldap wrapper. + +2004-11-12 Werner Koch + + * http.c (insert_escapes): Print the percent sign too. + + * dirmngr-client.c (inq_cert): Ignore "SENDCERT" and + "SENDISSUERCERT". + + * server.c (do_get_cert_local): Limit the length of a retruned + certificate. Return NULL without an error if an empry value has + been received. + + * crlfetch.c (ca_cert_fetch): Use the ksba_reader_object. + (setup_funopen, fun_reader, fun_closer): Removed. + + * crlcache.c (get_issuer_cert): Adjust accordingly. + + * ldap.c (attr_fetch_ldap_internal, attr_fetch_fun_closer) + (attr_fetch_fun_reader, url_fetch_ldap_internal) + (get_attr_from_result_ldap): Removed. + (destroy_wrapper, print_log_line, ldap_wrapper_thread) + (ldap_wrapper_release_context, reader_callback, ldap_wrapper) + (run_ldap_wrapper): New. + (url_fetch_ldap): Make use of the new ldap wrapper and return a + ksba reader object instead of a stdio stream. + (attr_fetch_ldap): Ditto. + (make_url, escape4url): New. + +2004-11-11 Werner Koch + + * dirmngr.c (launch_ripper_thread): New. + (main): Start it wheere appropriate. Always ignore SIGPIPE. + (start_connection_thread): Maintain a connection count. + (handle_signal, handle_connections): Use it here instead of the + thread count. + + * crlcache.c (crl_cache_insert): Changed to use ksba reader + object. Changed all callers to pass this argument. + +2004-11-08 Werner Koch + + * dirmngr_ldap.c: New. + + * crlcache.c (crl_cache_init): Don't return a cache object but + keep it module local. We only need one. + (crl_cache_deinit): Don't take cache object but work on existing + one. + (get_current_cache): New. + (crl_cache_insert, crl_cache_list, crl_cache_load): Use the global + cache object and removed the cache arg. Changed all callers. + + * dirmngr-client.c: New option --ping. + + * dirmngr.c (main): New option --daemon. Initialize PTH. + (handle_connections, start_connection_thread): New. + (handle_signal): New. + (parse_rereadable_options): New. Changed main to make use of it. + (set_debug): Don't bail out on invalid debug levels. + (main): Init the crl_chache for server and daemon mode. + + * server.c (start_command_handler): New arg FD. Changed callers. + +2004-11-06 Werner Koch + + * server.c (map_assuan_err): Factored out to .. + * maperror.c: .. new file. + * util.h: Add prototype + +2004-11-05 Werner Koch + + * no-libgcrypt.c: New, used as helper for dirmngr-client which + does not need libgcrypt proper but jnlib references the memory + functions. Taken from gnupg 1.9.12. + + * dirmngr.h: Factored i18n and xmalloc code out to .. + * i18n.h, util.h: .. New. + + * dirmngr-client.c: New. Some code taken from gnupg 1.9.12. + * Makefile.am (bin_PROGRAMS) Add dirmngr-client. + +2004-11-04 Werner Koch + + * src/server.c (get_fingerprint_from_line, cmd_checkcrl) + (cmd_checkocsp): New. + (register_commands): Register new commands. + (inquire_cert_and_load_crl): Factored most code out to .. + (reload_crl): .. new function. + * src/certcache.h, src/certcache.c: New. + * src/Makefile.am (dirmngr_SOURCES): Add new files. + +2004-11-04 Werner Koch + + Please note that earlier entries are found in the top level + ChangeLog. + [Update after merge with GnuPG: see ./ChangeLog.1] + + + Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/dirmngr/ChangeLog.1 b/dirmngr/ChangeLog.1 new file mode 100644 index 000000000..6d7a513e2 --- /dev/null +++ b/dirmngr/ChangeLog.1 @@ -0,0 +1,802 @@ +There are old Dirmngr ChangeLog entries. + +2004-10-04 Werner Koch + + * src/dirmngr.c: Changed an help entry description. + +2004-09-30 Werner Koch + + * src/dirmngr.c (i18n_init): Always use LC_ALL. + +2004-09-28 Werner Koch + + Released 0.5.6. + + * config.guess, config.sub: Updated. + +2004-06-21 Werner Koch + + * src/crlfetch.c (crl_fetch): Bad hack to use the right attribute. + +2004-05-13 Werner Koch + + Released 0.5.5. + + * src/ldap.c (start_cert_fetch_ldap, start_cert_fetch_ldap): More + detailed error messages. + + * src/crlcache.c (update_dir): Handle i-records properly. + +2004-04-29 Werner Koch + + Released 0.5.4. + + * src/crlcache.h (crl_cache_result_t): Add CRL_CACHE_CANTUSE. + * src/server.c (cmd_isvalid): Handle it here. + * src/crlcache.c (crl_cache_isvalid): Issue this code if the CRL + cant be used. + (open_dir): Parse new fields 8,9 and 10 as well as the invalid flag. + (write_dir_line_crl): Write new fields. + (get_crl_number, get_auth_key_id): New. + (crl_cache_insert): Fill new fields. Mark the entry invalid if + the CRL is too old after an update or an unknown critical + extension was seen. + (list_one_crl_entry): Print the new fields. + +2004-04-28 Werner Koch + + * configure.ac: Requires libksba 0.9.6. + + * src/dirmngr.c: New option --ocsp-signer. + * src/dirmngr.h (opt): Renamed member OCSP_REPONDERS to + OCSP_RESPONDER and made ist a simple string. Add OCSP_SIGNER. + * src/ocsp.c (ocsp_isvalid): Changed it accordingly. + (ocsp_isvalid): Pass the ocsp_signer to check_signature. + (check_signature): New arg SIGNER_FPR. Use it to retrieve the + certificate. Factored out common code to .. + (check_signature_core): .. New. + +2004-04-27 Werner Koch + + * src/server.c (start_command_handler): Keep track of the first + connection. + (dirmngr_tick): New. + * src/ldap.c (attr_fetch_fun_reader): Call it from time to time. + +2004-04-23 Werner Koch + + * src/dirmngr.c (main): Removed the add-servers option from the + gpgconf list. It is not really useful. + +2004-04-02 Thomas Schwinge + + * autogen.sh: Added ACLOCAL_FLAGS. + +2004-04-13 Werner Koch + + * src/crlcache.c (update_dir): Do not double close FPOUT. + +2004-04-09 Werner Koch + + * src/cdblib.c (cdb_make_start): Wipeout the entire buffer to + shutup valgrind. + (ewrite): Fixed writing bad data on EINTR. + + * src/ldap.c (get_attr_from_result_ldap): Fixed bad copy and + terminate of a string. + + * src/crlfetch.c (crl_fetch): Fixed freeing of VALUE on error. + +2004-04-07 Werner Koch + + * src/dirmngr.h (server_control_s): Add member force_crl_refresh. + * src/server.c (option_handler): New. + (start_command_handler): Register option handler + * src/crlcache.c (crl_cache_isvalid): Add arg FORCE_REFRESH. + (crl_cache_insert): Record last refresh in memory. + + * src/server.c (inquire_cert_and_load_crl): Renamed from + inquire_cert. + +2004-04-06 Werner Koch + + Released 0.5.3 + + * doc/dirmngr.texi: Updated. + * doc/texinfo.tex: Updated. + +2004-04-05 Werner Koch + + * src/ocsp.c (ocsp_isvalid): Check THIS_UPDATE. + + * src/misc.c (add_isotime): New. + (date2jd, jd2date, days_per_month, days_per_year): New. Taken from + my ancient (1988) code used in Wedit (time2.c). + +2004-04-02 Werner Koch + + * autogen.sh: Check gettext version. + * configure.ac: Add AM_GNU_GETTEXT. + +2004-04-02 gettextize + + * Makefile.am (SUBDIRS): Add intl. + (EXTRA_DIST): Add config.rpath. + * configure.ac (AC_CONFIG_FILES): Add intl/Makefile, + +2004-04-02 Werner Koch + + Add i18n at most places. + + * src/dirmngr.c (i18n_init): New. + (main): Call it. + * src/dirmngr.h: Add i18n stuff. + +2004-04-01 Werner Koch + + * src/misc.c (get_fingerprint_hexstring): New. + + * src/server.c (dirmngr_status): New. + +2004-03-26 Werner Koch + + * configure.ac: Add AC_SYS_LARGEFILE. + + * doc/dirmngr.texi: Changed the license to the GPL as per message + by Mathhias Kalle Dalheimer of Klaralvdalens-Datakonsult dated + Jan 7, 2004. + * doc/fdl.texi: Removed. + +2004-03-25 Werner Koch + + * src/dirmngr.c (main): New command --fetch-crl. + +2004-03-23 Werner Koch + + * src/dirmngr.c: New option --allow-ocsp. + * src/server.c (cmd_isvalid): Make use of allow_ocsp. + +2004-03-17 Werner Koch + + * src/dirmngr.c (main) : Fixed default value quoting. + +2004-03-16 Werner Koch + + * src/dirmngr.c (main): Add ocsp-responder to the gpgconf list. + Add option --debug-level. + (set_debug): New. + +2004-03-15 Werner Koch + + * src/misc.c (canon_sexp_to_grcy): New. + +2004-03-12 Werner Koch + + * src/crlfetch.c (crl_fetch): Hack to substitute http for https. + +2004-03-10 Werner Koch + + * src/dirmngr.c (parse_ldapserver_file): Don't skip the entire + file on errors. + +2004-03-09 Werner Koch + + * src/dirmngr.c (my_ksba_hash_buffer): New. + (main): Initialize the internal libksba hashing. + + * src/server.c (get_issuer_cert_local): Renamed to ... + (get_cert_local): ... this. Changed all callers. Allow NULL for + ISSUER to return the current target cert. + (get_issuing_cert_local): New. + (do_get_cert_local): Moved common code to here. + +2004-03-06 Werner Koch + + Released 0.5.2. + + * configure.ac: Fixed last change to check the API version of + libgcrypt. + +2004-03-05 Werner Koch + + * configure.ac: Also check the SONAME of libgcrypt. + +2004-03-03 Werner Koch + + * src/dirmngr.c: New option --ocsp-responder. + * src/dirmngr.h (opt): Add member OCSP_RESPONDERS. + +2004-02-26 Steffen Hansen + + * src/server.c (start_command_handler): Corrected typo and made + dirmngr output it's version in the greeting message. + +2004-02-24 Marcus Brinkmann + + * src/dirmngr.c (DEFAULT_ADD_SERVERS): Removed. If this were + true, there'd be no way to disable it. + (main): Dump options in new gpgconf format. + +2004-02-11 Werner Koch + + * autogen.sh (check_version): Removed bashism and simplified. + +2004-02-06 Moritz Schulte + + * src/crlfetch.c (crl_fetch_default): Do not dereference VALUE, + when checking for non-zero. + +2004-02-01 Marcus Brinkmann + + * src/dirmngr.c (DEFAULT_ADD_SERVERS, DEFAULT_MAX_REPLIES) + (DEFAULT_LDAP_TIMEOUT): New macros. + (main): Use them. + (enum cmd_and_opt_values): New command aGPGConfList. + (main): Add handler here. + +2004-01-17 Werner Koch + + * configure.ac: Added AC_CHECK_FUNCS tests again, because the + other test occurrences belong to the jnlib tests block. + +2004-01-15 Moritz Schulte + + * configure.ac: Fixed funopen replacement mechanism; removed + unnecessary AC_CHECK_FUNCS calls. + +2004-01-14 Werner Koch + + * src/crlcache.c (list_one_crl_entry): Don't use putchar. + + * src/server.c (cmd_listcrls): New. + +2003-12-23 Werner Koch + + Released 0.5.1. + +2003-12-17 Werner Koch + + * configure.ac (CFLAGS): Add -Wformat-noliteral in gcc + + maintainer mode. + (NEED_LIBASSUAN_VERSION): Bump up to 0.6.2. + +2003-12-16 Werner Koch + + * configure.ac: Update the tests for jnlib. + * src/dirmngr.c (main): Ignore SIGPIPE in server mode. + +2003-12-12 Werner Koch + + * src/crlcache.c (hash_dbfile): Also hash version info of the + cache file format. + + * src/Makefile.am (dirmngr_SOURCES): Add http.h. + + * configure.ac: Removed checking for DB2. Add checking for mmap. + * src/cdb.h, src/cdblib.h: New. Add a few comments from the + original man page and fixed typos. + * src/cdblib.c (cdb_findinit, cdb_findnext): Modified to allow + walking over all entries. + * src/crlcache.h: Removed DB2/4 cruft. + (release_one_cache_entry, lock_db_file, crl_parse_insert) + (crl_cache_insert, crl_cache_isvalid, list_one_crl_entry): Use the + new CDB interface. + + * src/dirmngr.c: Beautified the help messages. + (wrong_args): New. + (main): new option --force. Revamped the command handling code. + Allow to pass multiple CRLS as well as stdin to --local-crl. + * src/crlcache.c (crl_cache_insert): Make --force work. + +2003-12-11 Werner Koch + + * src/crlfetch.c (crl_fetch): Enhanced to allow fetching binary + data using HTTP. + * src/http.c, src/http.h: Replaced by the code from gnupg 1.3 and + modified acording to our needs. + (read_line): New. Based on the code from GnuPG's iobuf_read_line. + * configure.ac: Check for getaddrinfo. + + * src/dirmngr.c (parse_ldapserver_file): Close the stream. + (main): Free ldapfile. + + * src/ocsp.c, src/ocsp.h: New. Albeit not functionality. + + * src/server.c (inquire_cert): Catch EOF when reading dist points. + + * src/crlcache.c (hash_dbfile, check_dbfile): New. + (lock_db_file, crl_cache_insert): Use them here to detect + corrupted CRL files. + (open_dir): Read the new dbfile hash field. + + * src/crlfetch.c (crl_fetch, crl_fetch_default): Changed to retrun + a stream. + (fun_reader, fun_closer, setup_funopen): New. + * src/server.c (inquire_cert): Changed to use the new stream interface + of crlfetch.c. + +2003-12-10 Werner Koch + + * src/funopen.c: New. + * configure.ac (funopen): Add test. + * src/Makefile.am (dirmngr_LDADD): Add LIBOBJS. + + * src/crlcache.c (next_line_from_file): Remove the limit on the + line length. + (crl_cache_new): Removed. + (open_dbcontent): New. + (crl_cache_init): Use it here. + (crl_cache_flush): The DB content fie is now in the cache + directory, so we can simplify it. + (make_db_file_name, lock_db_file, unlock_db_file): New. + (release_cache): Close the cached DB files. + (crl_cache_isvalid): Make use of the new lock_db_file. + (crl_cache_insert): Changed to take a stream as argument. + (crl_parse_insert): Rewritten to use a temporary DB and to avoid + using up large amounts of memory. + (db_entry_new): Removed. + (release_cache,release_one_cache_entry): Splitted up. + (find_entry): Take care of the new deleted flag. + (crl_cache_load): Simplified becuase we can now pass a FP to the + insert code. + (save_contents): Removed. + (update_dir): New. + (open_dbcontent_file): Renamed to open_dir_file. + (check_dbcontent_version): Renamed to check_dir_version. + (open_dbcontent): Renamed to open_dir. + + * src/dirmngr.c: New option --faked-system-time. + * src/misc.c (faked_time_p, set_time, get_time): New. Taken from GnuPG. + (check_isotime): New. + (unpercent_string): New. + +2003-12-09 Werner Koch + + * src/crlcache.h (DBDIR,DBCONTENTFILE): Changed value. + + * autogen.sh: Reworked. + * README.CVS: New. + * configure.ac: Added min_automake_version. + +2003-12-03 Werner Koch + + * src/server.c (cmd_lookup): Send an END line after each + certificate. + +2003-11-28 Werner Koch + + * src/Makefile.am (dirmngr_LDADD): Remove DB_LIBS + because it never got defined and -ldb{2,4} is implictly set + by the AC_CHECK_LIB test in configure. + + * src/crlcache.c (mydbopen): DB4 needs an extra parameter; I + wonder who ever tested DB4 support. Add an error statement in + case no DB support is configured. + + * tests/Makefile.am: Don't use AM_CPPFLAGS but AM_CFLAGS, replaced + variables by configure templates. + * src/Makefile.am: Ditto. + +2003-11-19 Werner Koch + + * src/crlcache.c (list_one_crl_entry): Define X to nothing for non + DB4 systems. Thanks to Luca M. G. Centamore. + +2003-11-17 Werner Koch + + Released 0.5.0 + + * src/crlcache.c (crl_cache_new): Fixed eof detection. + + * src/server.c (cmd_loadcrl): Do the unescaping. + + * doc/dirmngr.texi: Added a history section for this modified + version. + +2003-11-14 Werner Koch + + * tests/asschk.c: New. Taken from GnuPG. + * tests/Makefile.am: Added asschk. + +2003-11-13 Werner Koch + + * src/ldap.c (fetch_next_cert_ldap): Get the pattern switching + right. + + * tests/test-dirmngr.c: Replaced a couple of deprecated types. + + * configure.ac (GPG_ERR_SOURCE_DEFAULT): Added. + (fopencookie, asprintf): Removed unneeded test. + (PRINTABLE_OS_NAME): Updated the test from gnupg. + (CFLAGS): Do full warnings only in maintainer mode. Add flag + --enable gcc-warnings to override it and to enable even more + warnings. + * acinclude.m4: Removed the libgcrypt test. + + * src/ldap.c (get_attr_from_result_ldap): Simplified the binary + hack and return a proper gpg error. + (attr_fetch_ldap_internal): Changed error handling. + (attr_fetch_ldap): Reworked. Return configuration error if no + servers are configured. + (url_fetch_ldap, add_server_to_servers) + (url_fetch_ldap_internal): Reworked. + (struct cert_fetch_context_s): New to get rid of a global state. + (start_cert_fetch_ldap): Allocate context and do a bind with a + timeout. Parse pattern. + (end_cert_fetch_ldap): Take context and don't return anything. + (find_next_pattern): Removed. + (parse_one_pattern): Redone. + (get_cert_ldap): Redone. + * src/server.c (cmd_lookup): Changed for changed fetch functions. + + * doc/dirmngr.texi: Reworked a bit to get rid of tex errors. + + * configure.ac: Enable makeinfo test. + + * src/crlcache.c (crl_cache_insert): Fixed for latest KSBA API + changes. + * tests/test-dirmngr.c (main): Ditto. Also added some more error + checking. + +2003-11-11 Werner Koch + + * src/cert.c (hashify_data, hexify_data, serial_hex) + (serial_to_buffer): Moved all to ... + * src/misc.c: .. here. + * src/Makefile.am (cert.c, cert.h): Removed. + * cert.c, cert.h: Removed. + + * m4/: New. + * configure.ac, Makefile.am: Include m4 directory support, updated + required library versions. + + * src/cert.c (make_cert): Removed. + + * src/ldap.c (fetch_next_cert_ldap): Return a gpg style error. + + * src/misc.h (copy_time): New. + * src/misc.c (get_isotime): New. + (iso_string2time, iso_time2string): Removed. + (unhexify): New. + + * src/crlcache.h (DBCONTENTSVERSION): Bumbed to 0.6. + * src/crlcache.c (finish_sig_check): New. Factored out from + crl_parse_insert and entirely redone. + (do_encode_md): Removed. + (print_time): Removed + (crl_cache_isvalid): Reworked. + +2003-11-10 Werner Koch + + * src/crlcache.c (make_db_val, parse_db_val): Removed. + + * src/cert.c (serial_to_buffer): New. + + * src/server.c (get_issuer_cert_local): Rewritten. + + * src/crlcache.c (crl_parse_insert): Rewritten. Takes now a CTRL + instead of the Assuan context. Changed caller accordingly. + (get_issuer_cert): Cleaned up. + + * src/crlfetch.c (crl_fetch): Changed VALUE to unsigned char* for + documentation reasons. Make sure that VALUE is released on error. + (crl_fetch_default, ca_cert_fetch): Ditto. + + * src/crlcache.c (release_cache): New. + (crl_cache_deinit): Use it here. + (crl_cache_flush): Redone. + (save_contents): Redone. + (crl_cache_list, list_one_crl_entry): Print error messages. + +2003-11-06 Werner Koch + + * src/crlcache.c (create_directory_if_needed, cleanup_cache_dir): + New. Factored out from crl_cache_new and mostly rewritten. + (crl_cache_new): Rewritten. + (next_line_from_file): New. + (find_entry): Cleaned up. + (crl_cache_deinit): Cleaned up. + + * src/dirmngr.c (dirmngr_init_default_ctrl): New stub. + * src/dirmngr.h (ctrl_t): New. + (DBG_ASSUAN,...): Added the usual debug test macros. + * src/server.c: Removed the GET_PTR cruft, replaced it by ctrl_t. + Removed the recursion flag. + (get_issuer_cert_local): Allow for arbitary large + certificates. 4096 is definitely too small. + (inquire_cert): Ditto. + (start_command_handler): Set a hello line and call the default + init function. + (cmd_isvalid): Rewritten. + (inquire_cert): Removed unused arg LINE. General cleanup. + (map_assuan_err,map_to_assuan_status): New. Taken from gnupg 1.9. + (cmd_lookup): Rewritten. + (cmd_loadcrl): Started to rewrite it. + +2003-10-29 Werner Koch + + * src/dirmngr.c (parse_ldapserver_file): Entirely rewritten. + (cleanup): New. + (main): Cleaned up. + +2003-10-28 Werner Koch + + * src/dirmngr.h: Renamed dirmngr_opt to opt. + + * src/dirmngr.c (parse_ldapserver_file, free_ldapservers_list): + Moved with this file. Cleaned up. Replaced too deep recursion in + the free function. + +2003-10-21 Werner Koch + + Changed all occurrences of assuan.h to use use the system provided + one. + * src/server.c (register_commands): Adjusted for Assuan API change. + +2003-08-14 Werner Koch + + * src/Makefile.am: s/LIBKSBA_/KSBA_/. Changed for external Assuan lib. + * tests/Makefile.am: Ditto. + + * configure.ac: Partly restructured, add standard checks for + required libraries, removed included libassuan. + * Makefile.am (SUBDIRS): Removed assuan becuase we now use the + libassuan package. + + * src/dirmngr.c (main): Properly initialize Libgcrypt and libksba. + +2003-08-13 Werner Koch + + * src/server.c (get_issuer_cert_local): Print error using + assuan_strerror. + + * src/crlcache.c (do_encode_md, start_sig_check): Adjust for + changed Libgcrypt API. + +2003-06-19 Steffen Hansen + + * configure.ac: Upped version to 0.4.7-cvs. + +2003-06-19 Steffen Hansen + + * configure.ac: Release 0.4.6. + +2003-06-17 Bernhard Reiter + + * src/ldap.c (url_fetch_ldap()): + try other default servers when an url with hostname failed + * AUTHORS: added Steffen and Werner + * THANKS: Thanked people in the ChangeLog and the Ägypten-Team + + +2003-06-16 Steffen Hansen + + * configure.ac, src/crlcache.h, src/crlcache.c: Added db4 support. + * src/Makefile.am, tests/Makefile.am: Removed automake warning. + * tests/test-dirmngr.c: Removed a warning. + +2003-05-12 Steffen Hansen + + * doc/Makefile.am: Added dirmngr.ops to DISTCLEANFILES. + * ChangeLog, doc/ChangeLog, src/ChangeLog: Merged dirmngr ChangeLogs + into one toplevel file. + * acinclude.m4, configure.ac: Renamed PFX to PATH for consistency. + +2003-05-12 Steffen Hansen + + * src/ldap.c: Fixed end-of-certificates-list indication. + +2003-05-08 Steffen Hansen + + * src/server.c: Fixed iteration over server list + +2003-02-23 Steffen Hansen + + * src/crlcache.h, src/crlcache.c, src/dirmngr.c: Implemented --flush command. + +2003-02-07 Marcus Brinkmann + + * configure.ac: Release 0.4.4. + +2003-02-05 Steffen Hansen + + * src/ldap.c: Try harder with and without ";binary" in the + attribute name when fetching certificates. + * src/ldap.c, src/server.c: Support multiple userCertificate attributes + per entry. + +2003-02-04 Steffen Hansen + + * src/ldap.c: Include the sn attribute in the search filter. + Better log messages. + +2002-11-20 Steffen Hansen + + * Doc updates (fixes #1373) + * Fix for #1419 (crash in free_ldapservers_list()) + * Fix for #1375. Dirmngr now asks back with an INQUIRE SENDCERT before + querying the LDAP servers for an issuer certificate to validate a CRL + +2002-11-12 Werner Koch + + * config.sub, config.guess: Updated from ftp.gnu.org/gnu/config + to version 2002-11-08. + +2002-11-12 Werner Koch + + * dirmngr.c (main) : Better pass NULL instead + of an unitialized Assuan context. Let's hope that the other + functions can cope with this. + +2002-10-25 Bernhard Reiter + + * src/ldap.c (get_attr_from_result_ldap()): + added value extraction retry for CRLs and Certs without ";binary" + * changed version number to reflect cvs status to "0.4.3-cvs" + +2002-08-21 Werner Koch + + * dirmngr.c (main): Changed default homedir to .gnupg. + +2002-08-07 Steffen Hansen + + * Added configure check to examine whether db2 cursor() uses 3 or + 4 parameters. + +2002-07-31 Werner Koch + + * doc/dirmngr.texi: Fixed the structure and added menu entries + for the other nodes. + +2002-07-30 Steffen Hansen + + * Added doc dir and first steps towards manual. + +2002-07-29 Steffen Hansen + + * Got rid of the default server for CRL lookup. We now use the + same list of servers that we use for cert. lookup. + +2002-07-29 Steffen Hansen + + * New option --add-servers to allow dirmngr to add LDAP servers + found in CRL distribution points to the list of servers it + searches. NOTE: The added servers are only active in the currently + running dirmngr -- the info isn't written to persistens storage. + +2002-07-26 Steffen Hansen + + * Default LDAP timeout is 100 seconds now. + + * Use DB2 instead of DB1. Check for libresolv, fixed bug when + libldap was found in the default search path. + +2002-07-22 Steffen Hansen + + * Implemented --load-crl option. Also available as + LOADCRL assuan command when in server mode. + +2002-07-22 Steffen Hansen + + * Implemented new option --ldaptimeout to specify the number of seconds to + wait for an LDAP request before timeout. + + * Added --list-crls option to print the contents of the CRL cache + * Added some items to the dbcontents file to make printout nicer + and updated it's version number + +2002-07-02 Werner Koch + + * crlcache.c (crl_parse_insert): Fixed log_debug format string. + +2002-07-02 Steffen Hansen + + * configure.ac: Use DB->get() return value correctly. + +2002-06-28 Werner Koch + + * crlcache.c (crl_parse_insert): Keep track of newly allocated + ENTRY so that we don't free existing errors after a bad signature. + + * dirmngr.h: Include prototype for start_command_handler. + + * crlfetch.c, crlcache.c, http.c, cert.c, ldap.c: Include + config.h. + + * crlcache.c (crl_parse_insert): Fixed format type specifiers for + time_t variables in log_debug. + + * error.h: Use log_debug instead of dirmngr_debug. Changed all + callers. + * Makefile.am (dirmngr_SOURCES): Removed error.c + + * dirmngr.c (main): Register gcrypt malloc functions with ksba so + that we don't run into problems by using the wrong free function. + The gcrypt malloc function have the additional benefit of a + providing allocation sanity checks when compiled with that + feature. + + * crlcache.c (get_issuer_cert): Use xfree instead of ksba_free. + + +2002-06-27 Steffen Hansen + + * ldap.c: Look for both userCertificate and caCertificate + +2002-06-26 Steffen Hansen + + * configure.ac: Upped version number to 0.3.1 + +2002-06-25 Werner Koch + + * server.c (cmd_lookup): Use assuan_write_status which ensures a + correct syntax. + +2002-06-20 Werner Koch + + * crlcache.c (crl_cache_isvalid): Started with some nicer logging. + However, this will need a lot more work. + (get_issuer_cert): Ditto. + + * dirmngr.c (main): Changed required libgcrypt version and don't + print the prefix when using a logfile. + +2002-06-20 Werner Koch + + * tests/Makefile.am (TESTS): Removed test-dirmngr because it + is not a proper test program. + (EXTRA_DIST): Removed the non-existent test certificate. + +2002-05-21 Werner Koch + + * server.c (start_command_handler): Enable assuan debugging. + +2002-05-08 Steffen Hansen + + * Replaced gdbm check with db1 check + +2002-05-08 Steffen Hansen + + * Replaced gdbm with db1, updated file format version + +2002-03-01 Steffen Hansen + + * Added gdbm configure check + +2002-01-23 Steffen Hansen + + * Return ASSUAN_CRL_Too_Old if the CRL is too old + + +2002-01-17 Steffen Hansen + + Added commandline options --ldapserver --ldapport + --ldapuser --ldappassword . + + Cleaned up CRL parsing, signature evaluation a bit, changed + datetime format in config file to ISO, added version string to + contents format and cache file clean up code in case of mismatch. + +2002-01-14 Steffen Hansen + + * Use dirmngr_opt.homedir for storing the db. Added Makefile.am to + tests, bugfixes. + + * First code. + Things that work: + Loading/saving database (paths hardcoded) + Fetching CRL from hardcoded server, parsing and inserting in database + Answer ISVALID xxx.yyy requests + + Things that are missing: + Some error-checking/handling + Proper autoconf handling of gdbm and OpenLDAP + Signature checking downloaded CRLs + Answer LOOKUP requests + ... + + How to test: + cd tests + ldapsearch -v -x -h www.trustcenter.de -b '' userCertificate -t + cp /tmp/ testcert.der + ./test-dirmngr diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am new file mode 100644 index 000000000..b4ea8c66a --- /dev/null +++ b/dirmngr/Makefile.am @@ -0,0 +1,65 @@ +# Makefile.am - dirmngr +# Copyright (C) 2002 Klarälvdalens Datakonsult AB +# Copyright (C) 2004, 2007, 2010 g10 Code GmbH +# +# 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 . + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = OAUTHORS ONEWS ChangeLog.1 + +bin_PROGRAMS = dirmngr dirmngr-client + +libexec_PROGRAMS = dirmngr_ldap + +AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common + +include $(top_srcdir)/am/cmacros.am + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(PTH_CFLAGS) + +BUILT_SOURCES = no-libgcrypt.c + +noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h + +dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ + ldapserver.h ldapserver.c certcache.c certcache.h \ + b64dec.c cdb.h cdblib.c ldap.c http.c http.h misc.c \ + ocsp.c ocsp.h validate.c validate.h + +dirmngr_LDADD = $(libcommonpth) $(DNSLIBS) $(LIBASSUAN_LIBS) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(PTH_LIBS) $(LIBINTL) $(LIBICONV) + +if HAVE_W32_SYSTEM +ldap_url = ldap-url.h ldap-url.c +else +ldap_url = +endif + +dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url) no-libgcrypt.c +dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) +dirmngr_ldap_LDFLAGS = +dirmngr_ldap_LDADD = $(libcommon) $(DNSLIBS) \ + $(GPG_ERROR_LIBS) $(LDAPLIBS) $(LIBINTL) $(LIBICONV) + +dirmngr_client_SOURCES = dirmngr-client.c b64enc.c no-libgcrypt.c +dirmngr_client_LDADD = $(libcommon) $(LIBASSUAN_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) + + +no-libgcrypt.c : $(top_srcdir)/tools/no-libgcrypt.c + cat $(top_srcdir)/tools/no-libgcrypt.c > no-libgcrypt.c diff --git a/dirmngr/OAUTHORS b/dirmngr/OAUTHORS new file mode 100644 index 000000000..f9adc324c --- /dev/null +++ b/dirmngr/OAUTHORS @@ -0,0 +1,40 @@ +The old AUTHORS file from the separate dirmngr package. + + Package: dirmngr + Maintainer: Werner Koch + Bug reports: bug-dirmngr@gnupg.org + Security related bug reports: security@gnupg.org + License: GPLv2+ + + +Steffen Hansen + - Initial code + +g10 Code GmbH + - All stuff written since October 2003. + +Werner Koch , + - Help with initial code. + +Free Software Foundation + - Code taken from GnuPG. + +Michael Tokarev + - src/cdb.h and src/cdblib.c from the public domain tinycdb 0.73. + + +The actual code is under the GNU GPL, except for src/cdb.h and +src/cdblib.h which are in the public domain. + + + Copyright 2003, 2004, 2006, 2007, 2008, 2010 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + diff --git a/dirmngr/ONEWS b/dirmngr/ONEWS new file mode 100644 index 000000000..a9ec4d77c --- /dev/null +++ b/dirmngr/ONEWS @@ -0,0 +1,240 @@ +These are NEWS entries from the old separate dirmngr package + +Noteworthy changes in version 1.1.0 (unreleased) +------------------------------------------------ + + * Fixed a resource problem with LDAP CRLs. + + * Fixed a bad EOF detection with HTTP CRLs. + + * Made "dirmngr-client --url --load-crl URL" work. + + * New option --ignore-cert-extension. + + * Make use of libassuan 2.0 which is available as a DSO. + + +Noteworthy changes in version 1.0.3 (2009-06-17) +------------------------------------------------ + + * Client based trust anchors are now supported. + + * Configured certificates with the suffix ".der" are now also used. + + * Libgcrypt 1.4 is now required. + + +Noteworthy changes in version 1.0.2 (2008-07-31) +------------------------------------------------ + + * New option --url for the LOOKUP command and dirmngr-client. + + * The LOOKUP command does now also consults the local cache. New + option --cache-only for it and --local for dirmngr-client. + + * Port to Windows completed. + + * Improved certificate chain construction. + + * Support loading of PEM encoded CRLs via HTTP. + + +Noteworthy changes in version 1.0.1 (2007-08-16) +------------------------------------------------ + + * The option --ocsp-signer may now take a filename to allow several + certificates to be valid signers for the default responder. + + * New option --ocsp-max-period and improved the OCSP time checks. + + * New option --force-default-signer for dirmngr-client. + + * Ported to Windows. + + +Noteworthy changes in version 1.0.0 (2006-11-29) +------------------------------------------------ + + * Bumbed the version number. + + * Removed included gettext. We now require the system to provide a + suitable installation. + + +Noteworthy changes in version 0.9.7 (2006-11-17) +------------------------------------------------ + + * Internal cleanups. + + * Fixed updating of DIR.txt. Add additional diagnostics. + + * Updated gettext package. + + +Noteworthy changes in version 0.9.6 (2006-09-04) +------------------------------------------------ + + * A couple of bug fixes for OCSP. + + * OCSP does now make use of the responder ID and optionally included + certificates in the response to locate certificates. + + * No more lost file descriptors when loading CRLs via HTTP. + + * HTTP redirection for CRL and OCSP has been implemented. + + * Man pages are now build and installed from the texinfo source. + + +Noteworthy changes in version 0.9.5 (2006-06-27) +------------------------------------------------ + + * Fixed a problems with the CRL caching and CRL certificate + validation. + + * Improved diagnostics. + + +Noteworthy changes in version 0.9.4 (2006-05-16) +------------------------------------------------ + + * Try all names of each crlDP. + + * Don't shutdown the socket after sending the HTTP request. + + +Noteworthy changes in version 0.9.3 (2005-10-26) +------------------------------------------------ + + * Minor bug fixes. + + +Noteworthy changes in version 0.9.2 (2005-04-21) +------------------------------------------------ + + * Make use of authorityKeyidentifier.keyIdentifier. + + * Fixed a possible hang on exit. + + +Noteworthy changes in version 0.9.1 (2005-02-08) +------------------------------------------------ + + * New option --pem for dirmngr-client to allow requesting service + using a PEM encoded certificate. + + * New option --squid-mode to allow using dirmngr-client directly as a + Squid helper. + + * Bug fixes. + + +Noteworthy changes in version 0.9.0 (2004-12-17) +------------------------------------------------ + + * New option --daemon to start dirmngr as a system daemon. This + switches to the use of different directories and also does + CRL signing certificate validation on its own. + + * New tool dirmngr-client. + + * New options: --ldap-wrapper-program, --http-wrapper-program, + --disable-ldap, --disable-http, --honor-http-proxy, --http-proxy, + --ldap-proxy, --only-ldap-proxy, --ignore-ldap-dp and + --ignore-http-dp. + + * Uses an external ldap wrapper to cope with timeouts and general + LDAP problems. + + * SIGHUP may be used to reread the configuration and to flush the + certificate cache. + + * An authorithyKeyIdentifier in a CRL is now handled correctly. + + +Noteworthy changes in version 0.5.6 (2004-09-28) +------------------------------------------------ + + * LDAP fix. + + * Logging fixes. + + * Updated some configuration files. + + +Noteworthy changes in version 0.5.5 (2004-05-13) +------------------------------------------------ + + * Fixed the growing-dir.txt bug. + + * Better LDAP error logging. + + +Noteworthy changes in version 0.5.4 (2004-04-29) +------------------------------------------------ + + * New commands --ocsp-responder and --ocsp-signer to define a default + OCSP reponder if a certificate does not contain an assigned OCSP + responder. + + +Noteworthy changes in version 0.5.3 (2004-04-06) +------------------------------------------------ + + * Basic OCSP support. + + +Noteworthy changes in version 0.5.2 (2004-03-06) +------------------------------------------------ + + * New Assuan command LISTCRLS. + + * A couple of minor bug fixes. + + +Noteworthy changes in version 0.5.1 (2003-12-23) +------------------------------------------------ + +* New options --faked-system-time and --force. + +* Changed the name of the cache directory to $HOMEDIR/dirmngr-cache.d + and renamed the dbcontents file. You may delete the now obsolete + cache/ directory and the dbcontents file. + +* Dropped DB2 or DB4 use. There is no need for it because a constant + database fits our needs far better. + +* Experimental support for retrieving CRLs via http. + +* The --log-file option may now be used to print logs to a socket. + Prefix the socket name with "socket://" to enable this. This does + not work on all systems and falls back to stderr if there is a + problem with the socket. + + +Noteworthy changes in version 0.5.0 (2003-11-17) +------------------------------------------------ + +* Revamped the entire thing. + +* Does now require Libgcrypt 1.1.90 or higher, as well as the latest + libksba and libassuan. + +* Fixed a bug in the assuan inquire processing. + + +Noteworthy changes as of 2002-08-21 +------------------------------------ + +* The default home directory is now .gnupg + + + Copyright 2003, 2004, 2005 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/dirmngr/b64dec.c b/dirmngr/b64dec.c new file mode 100644 index 000000000..af223aef2 --- /dev/null +++ b/dirmngr/b64dec.c @@ -0,0 +1,217 @@ +/* b64dec.c - Simple Base64 decoder. + * Copyright (C) 2008 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 "i18n.h" +#include "util.h" + + +/* The reverse base-64 list used for base-64 decoding. */ +static unsigned char const asctobin[128] = + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff + }; + +enum decoder_states + { + s_init, s_idle, s_lfseen, s_begin, + s_b64_0, s_b64_1, s_b64_2, s_b64_3, + s_waitendtitle, s_waitend + }; + + + +/* Initialize the context for the base64 decoder. If TITLE is NULL a + plain base64 decoding is done. If it is the empty string the + decoder will skip everything until a "-----BEGIN " line has been + seen, decoding ends at a "----END " line. + + Not yet implemented: If TITLE is either "PGP" or begins with "PGP " + the PGP armor lines are skipped as well. */ +gpg_error_t +b64dec_start (struct b64state *state, const char *title) +{ + memset (state, 0, sizeof *state); + if (title) + { + if (!strncmp (title, "PGP", 3) && (!title[3] || title[3] == ' ')) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + state->title = xtrystrdup (title); + if (!state->title) + return gpg_error_from_syserror (); + state->idx = s_init; + } + else + state->idx = s_b64_0; + return 0; +} + + +/* Do in-place decoding of base-64 data of LENGTH in BUFFER. Stores the + new length of the buffer at R_NBYTES. */ +gpg_error_t +b64dec_proc (struct b64state *state, void *buffer, size_t length, + size_t *r_nbytes) +{ + enum decoder_states ds = state->idx; + unsigned char val = state->radbuf[0]; + int pos = state->quad_count; + char *d, *s; + + if (state->stop_seen) + { + *r_nbytes = 0; + return gpg_error (GPG_ERR_EOF); + } + + for (s=d=buffer; length && !state->stop_seen; length--, s++) + { + switch (ds) + { + case s_idle: + if (*s == '\n') + { + ds = s_lfseen; + pos = 0; + } + break; + case s_init: + ds = s_lfseen; + case s_lfseen: + if (*s != "-----BEGIN "[pos]) + ds = s_idle; + else if (pos == 10) + ds = s_begin; + else + pos++; + break; + case s_begin: + if (*s == '\n') + ds = s_b64_0; + break; + case s_b64_0: + case s_b64_1: + case s_b64_2: + case s_b64_3: + { + int c; + + if (*s == '-' && state->title) + { + /* Not a valid Base64 character: assume end + header. */ + ds = s_waitend; + } + else if (*s == '=') + { + /* Pad character: stop */ + if (ds == s_b64_1) + *d++ = val; + ds = state->title? s_waitendtitle : s_waitend; + } + else if (*s == '\n' || *s == ' ' || *s == '\r' || *s == '\t') + ; /* Skip white spaces. */ + else if ( (*s & 0x80) + || (c = asctobin[*(unsigned char *)s]) == 255) + { + /* Skip invalid encodings. */ + state->invalid_encoding = 1; + } + else if (ds == s_b64_0) + { + val = c << 2; + ds = s_b64_1; + } + else if (ds == s_b64_1) + { + val |= (c>>4)&3; + *d++ = val; + val = (c<<4)&0xf0; + ds = s_b64_2; + } + else if (ds == s_b64_2) + { + val |= (c>>2)&15; + *d++ = val; + val = (c<<6)&0xc0; + ds = s_b64_3; + } + else + { + val |= c&0x3f; + *d++ = val; + ds = s_b64_0; + } + } + break; + case s_waitendtitle: + if (*s == '-') + ds = s_waitend; + break; + case s_waitend: + if ( *s == '\n') + state->stop_seen = 1; + break; + default: + BUG(); + } + } + + + state->idx = ds; + state->radbuf[0] = val; + state->quad_count = pos; + *r_nbytes = (d -(char*) buffer); + return 0; +} + + +/* This function needs to be called before releasing the decoder + state. It may return an error code in case an encoding error has + been found during decoding. */ +gpg_error_t +b64dec_finish (struct b64state *state) +{ + xfree (state->title); + state->title = NULL; + return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0; +} + diff --git a/dirmngr/b64enc.c b/dirmngr/b64enc.c new file mode 100644 index 000000000..4429a8e75 --- /dev/null +++ b/dirmngr/b64enc.c @@ -0,0 +1,213 @@ +/* b64enc.c - Simple Base64 encoder. + * Copyright (C) 2001, 2003, 2004 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include + +#include "i18n.h" +#include "util.h" + +#define B64ENC_DID_HEADER 1 +#define B64ENC_DID_TRAILER 2 +#define B64ENC_NO_LINEFEEDS 16 + + +/* The base-64 character list */ +static unsigned char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* Prepare for base-64 writing to the stream FP. If TITLE is not NULL + and not an empty string, this string will be used as the title for + the armor lines, with TITLE being an empty string, we don't write + the header lines and furthermore even don't write any linefeeds. + With TITLE beeing NULL, we merely don't write header but make sure + that lines are not too long. Note, that we don't write any output + unless at least one byte get written using b64enc_write. */ +gpg_error_t +b64enc_start (struct b64state *state, FILE *fp, const char *title) +{ + memset (state, 0, sizeof *state); + state->fp = fp; + if (title && !*title) + state->flags |= B64ENC_NO_LINEFEEDS; + else if (title) + { + state->title = strdup (title); + if (!state->title) + return gpg_error_from_errno (errno); + } + return 0; +} + + +/* Write NBYTES from BUFFER to the Base 64 stream identified by + STATE. With BUFFER and NBYTES being 0, merely do a fflush on the + stream. */ +gpg_error_t +b64enc_write (struct b64state *state, const void *buffer, size_t nbytes) +{ + unsigned char radbuf[4]; + int idx, quad_count; + const unsigned char *p; + FILE *fp = state->fp; + + + if (!nbytes) + { + if (buffer && fflush (fp)) + goto write_error; + return 0; + } + + if (!(state->flags & B64ENC_DID_HEADER)) + { + if (state->title) + { + if ( fputs ("-----BEGIN ", fp) == EOF + || fputs (state->title, fp) == EOF + || fputs ("-----\n", fp) == EOF) + goto write_error; + } + state->flags |= B64ENC_DID_HEADER; + } + + idx = state->idx; + quad_count = state->quad_count; + assert (idx < 4); + memcpy (radbuf, state->radbuf, idx); + + for (p=buffer; nbytes; p++, nbytes--) + { + radbuf[idx++] = *p; + if (idx > 2) + { + char tmp[4]; + + tmp[0] = bintoasc[(*radbuf >> 2) & 077]; + tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; + tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; + tmp[3] = bintoasc[radbuf[2]&077]; + for (idx=0; idx < 4; idx++) + putc (tmp[idx], fp); + idx = 0; + if (ferror (fp)) + goto write_error; + if (++quad_count >= (64/4)) + { + quad_count = 0; + if (!(state->flags & B64ENC_NO_LINEFEEDS) + && fputs ("\n", fp) == EOF) + goto write_error; + } + } + } + memcpy (state->radbuf, radbuf, idx); + state->idx = idx; + state->quad_count = quad_count; + return 0; + + write_error: + return gpg_error_from_errno (errno); +} + +gpg_error_t +b64enc_finish (struct b64state *state) +{ + gpg_error_t err = 0; + unsigned char radbuf[4]; + int idx, quad_count; + FILE *fp; + + if (!(state->flags & B64ENC_DID_HEADER)) + goto cleanup; + + /* Flush the base64 encoding */ + fp = state->fp; + idx = state->idx; + quad_count = state->quad_count; + assert (idx < 4); + memcpy (radbuf, state->radbuf, idx); + + if (idx) + { + char tmp[4]; + + tmp[0] = bintoasc[(*radbuf>>2)&077]; + if (idx == 1) + { + tmp[1] = bintoasc[((*radbuf << 4) & 060) & 077]; + tmp[2] = '='; + tmp[3] = '='; + } + else + { + tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; + tmp[2] = bintoasc[((radbuf[1] << 2) & 074) & 077]; + tmp[3] = '='; + } + for (idx=0; idx < 4; idx++) + putc (tmp[idx], fp); + idx = 0; + if (ferror (fp)) + goto write_error; + + if (++quad_count >= (64/4)) + { + quad_count = 0; + if (!(state->flags & B64ENC_NO_LINEFEEDS) + && fputs ("\n", fp) == EOF) + goto write_error; + } + } + + /* Finish the last line and write the trailer. */ + if (quad_count + && !(state->flags & B64ENC_NO_LINEFEEDS) + && fputs ("\n", fp) == EOF) + goto write_error; + + if (state->title) + { + if ( fputs ("-----END ", fp) == EOF + || fputs (state->title, fp) == EOF + || fputs ("-----\n", fp) == EOF) + goto write_error; + } + + goto cleanup; + + write_error: + err = gpg_error_from_errno (errno); + + cleanup: + if (state->title) + { + free (state->title); + state->title = NULL; + } + state->fp = NULL; + return err; +} + diff --git a/dirmngr/cdb.h b/dirmngr/cdb.h new file mode 100644 index 000000000..73cc9952e --- /dev/null +++ b/dirmngr/cdb.h @@ -0,0 +1,91 @@ +/* $Id: cdb.h 106 2003-12-12 17:36:49Z werner $ + * public cdb include file + * + * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru. + * Public domain. + * + * Taken from tinycdb-0.73. By Werner Koch 2003-12-12. + */ + +#ifndef TINYCDB_VERSION +#define TINYCDB_VERSION 0.73 + +typedef unsigned int cdbi_t; /*XXX should be at least 32 bits long */ + +/* common routines */ +cdbi_t cdb_hash(const void *buf, cdbi_t len); +cdbi_t cdb_unpack(const unsigned char buf[4]); +void cdb_pack(cdbi_t num, unsigned char buf[4]); + +struct cdb { + int cdb_fd; /* file descriptor */ + /* private members */ + cdbi_t cdb_fsize; /* datafile size */ + const unsigned char *cdb_mem; /* mmap'ed file memory */ + cdbi_t cdb_vpos, cdb_vlen; /* found data */ + cdbi_t cdb_kpos, cdb_klen; /* found key (only set if cdb_findinit + was called with KEY set to NULL). */ +}; + +#define cdb_datapos(c) ((c)->cdb_vpos) +#define cdb_datalen(c) ((c)->cdb_vlen) +#define cdb_keypos(c) ((c)->cdb_kpos) +#define cdb_keylen(c) ((c)->cdb_klen) +#define cdb_fileno(c) ((c)->cdb_fd) + +int cdb_init(struct cdb *cdbp, int fd); +void cdb_free(struct cdb *cdbp); + +int cdb_read(const struct cdb *cdbp, + void *buf, unsigned len, cdbi_t pos); +int cdb_find(struct cdb *cdbp, const void *key, unsigned klen); + +struct cdb_find { + struct cdb *cdb_cdbp; + cdbi_t cdb_hval; + const unsigned char *cdb_htp, *cdb_htab, *cdb_htend; + cdbi_t cdb_httodo; + const void *cdb_key; + cdbi_t cdb_klen; +}; + +int cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp, + const void *key, cdbi_t klen); +int cdb_findnext(struct cdb_find *cdbfp); + +/* old simple interface */ +/* open file using standard routine, then: */ +int cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp); +int cdb_bread(int fd, void *buf, int len); + +/* cdb_make */ + +struct cdb_make { + int cdb_fd; /* file descriptor */ + /* private */ + cdbi_t cdb_dpos; /* data position so far */ + cdbi_t cdb_rcnt; /* record count so far */ + char cdb_buf[4096]; /* write buffer */ + char *cdb_bpos; /* current buf position */ + struct cdb_rl *cdb_rec[256]; /* list of arrays of record infos */ +}; + + + +int cdb_make_start(struct cdb_make *cdbmp, int fd); +int cdb_make_add(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen); +int cdb_make_exists(struct cdb_make *cdbmp, + const void *key, cdbi_t klen); +int cdb_make_put(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen, + int flag); +#define CDB_PUT_ADD 0 /* add unconditionnaly, like cdb_make_add() */ +#define CDB_PUT_REPLACE 1 /* replace: do not place to index OLD record */ +#define CDB_PUT_INSERT 2 /* add only if not already exists */ +#define CDB_PUT_WARN 3 /* add unconditionally but ret. 1 if exists */ +int cdb_make_finish(struct cdb_make *cdbmp); + +#endif /* include guard */ diff --git a/dirmngr/cdblib.c b/dirmngr/cdblib.c new file mode 100644 index 000000000..de60fe926 --- /dev/null +++ b/dirmngr/cdblib.c @@ -0,0 +1,925 @@ +/* cdblib.c - all CDB library functions. + * + * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru. + * Public domain. + * + * Taken from tinycdb-0.73 and merged into one file for easier + * inclusion into Dirmngr. By Werner Koch 2003-12-12. + */ + +/* A cdb database is a single file used to map `keys' to `values', + having records of (key,value) pairs. File consists of 3 parts: toc + (table of contents), data and index (hash tables). + + Toc has fixed length of 2048 bytes, containing 256 pointers to hash + tables inside index sections. Every pointer consists of position + of a hash table in bytes from the beginning of a file, and a size + of a hash table in entries, both are 4-bytes (32 bits) unsigned + integers in little-endian form. Hash table length may have zero + length, meaning that corresponding hash table is empty. + + Right after toc section, data section follows without any + alingment. It consists of series of records, each is a key length, + value (data) length, key and value. Again, key and value length + are 4-byte unsigned integers. Each next record follows previous + without any special alignment. + + After data section, index (hash tables) section follows. It should + be looked to in conjunction with toc section, where each of max 256 + hash tables are defined. Index section consists of series of hash + tables, with starting position and length defined in toc section. + Every hash table is a sequence of records each holds two numbers: + key's hash value and record position inside data section (bytes + from the beginning of a file to first byte of key length starting + data record). If record position is zero, then this is an empty + hash table slot, pointed to nowhere. + + CDB hash function is + hv = ((hv << 5) + hv) ^ c + for every single c byte of a key, starting with hv = 5381. + + Toc section indexed by (hv % 256), i.e. hash value modulo 256 + (number of entries in toc section). + + In order to find a record, one should: first, compute the hash + value (hv) of a key. Second, look to hash table number hv modulo + 256. If it is empty, then there is no such key exists. If it is + not empty, then third, loop by slots inside that hash table, + starting from slot with number hv divided by 256 modulo length of + that table, or ((hv / 256) % htlen), searching for this hv in hash + table. Stop search on empty slot (if record position is zero) or + when all slots was probed (note cyclic search, jumping from end to + beginning of a table). When hash value in question is found in + hash table, look to key of corresponding record, comparing it with + key in question. If them of the same length and equals to each + other, then record is found, overwise, repeat with next hash table + slot. Note that there may be several records with the same key. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#ifdef _WIN32 +# include +#else +# include +# ifndef MAP_FAILED +# define MAP_FAILED ((void*)-1) +# endif +#endif +#include +#include "cdb.h" + +#ifndef EPROTO +# define EPROTO EINVAL +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif + + +struct cdb_rec { + cdbi_t hval; + cdbi_t rpos; +}; + +struct cdb_rl { + struct cdb_rl *next; + cdbi_t cnt; + struct cdb_rec rec[254]; +}; + +static int make_find(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, cdbi_t hval, + struct cdb_rl **rlp); +static int make_write(struct cdb_make *cdbmp, + const char *ptr, cdbi_t len); + + + +/* Initializes structure given by CDBP pointer and associates it with + the open file descriptor FD. Allocate memory for the structure + itself if needed and file open operation should be done by + application. File FD should be opened at least read-only, and + should be seekable. Routine returns 0 on success or negative value + on error. */ +int +cdb_init(struct cdb *cdbp, int fd) +{ + struct stat st; + unsigned char *mem; + unsigned fsize; +#ifdef _WIN32 + HANDLE hFile, hMapping; +#endif + + /* get file size */ + if (fstat(fd, &st) < 0) + return -1; + /* trivial sanity check: at least toc should be here */ + if (st.st_size < 2048) { + errno = EPROTO; + return -1; + } + fsize = (unsigned)(st.st_size & 0xffffffffu); + /* memory-map file */ +#ifdef _WIN32 + hFile = (HANDLE) _get_osfhandle(fd); + if (hFile == (HANDLE) -1) + return -1; + hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (!hMapping) + return -1; + mem = (unsigned char *)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + if (!mem) + return -1; +#else + mem = (unsigned char*)mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0); + if (mem == MAP_FAILED) + return -1; +#endif /* _WIN32 */ + + cdbp->cdb_fd = fd; + cdbp->cdb_fsize = st.st_size; + cdbp->cdb_mem = mem; + +#if 0 + /* XXX don't know well about madvise syscall -- is it legal + to set different options for parts of one mmap() region? + There is also posix_madvise() exist, with POSIX_MADV_RANDOM etc... + */ +#ifdef MADV_RANDOM + /* set madvise() parameters. Ignore errors for now if system + doesn't support it */ + madvise(mem, 2048, MADV_WILLNEED); + madvise(mem + 2048, cdbp->cdb_fsize - 2048, MADV_RANDOM); +#endif +#endif + + cdbp->cdb_vpos = cdbp->cdb_vlen = 0; + + return 0; +} + + +/* Frees the internal resources held by structure. Note that this + routine does not close the file. */ +void +cdb_free(struct cdb *cdbp) +{ + if (cdbp->cdb_mem) { +#ifdef _WIN32 + HANDLE hFile, hMapping; +#endif +#ifdef _WIN32 + hFile = (HANDLE) _get_osfhandle(cdbp->cdb_fd); + hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + UnmapViewOfFile((void*) cdbp->cdb_mem); + CloseHandle(hMapping); +#else + munmap((void*)cdbp->cdb_mem, cdbp->cdb_fsize); +#endif /* _WIN32 */ + cdbp->cdb_mem = NULL; + } + cdbp->cdb_fsize = 0; +} + + +/* Read data from cdb file, starting at position pos of length len, + placing result to buf. This routine may be used to get actual + value found by cdb_find() or other routines that returns position + and length of a data. Returns 0 on success or negative value on + error. */ +int +cdb_read(const struct cdb *cdbp, void *buf, unsigned len, cdbi_t pos) +{ + if (pos > cdbp->cdb_fsize || cdbp->cdb_fsize - pos < len) { + errno = EPROTO; + return -1; + } + memcpy(buf, cdbp->cdb_mem + pos, len); + return 0; +} + + +/* Attempts to find a key given by (key,klen) parameters. If key + exists in database, routine returns 1 and places position and + length of value associated with this key to internal fields inside + cdbp structure, to be accessible by cdb_datapos() and + cdb_datalen(). If key is not in database, routines returns 0. On + error, negative value is returned. Note that using cdb_find() it + is possible to lookup only first record with a given key. */ +int +cdb_find(struct cdb *cdbp, const void *key, cdbi_t klen) +{ + const unsigned char *htp; /* hash table pointer */ + const unsigned char *htab; /* hash table */ + const unsigned char *htend; /* end of hash table */ + cdbi_t httodo; /* ht bytes left to look */ + cdbi_t pos, n; + + cdbi_t hval; + + if (klen > cdbp->cdb_fsize) /* if key size is larger than file */ + return 0; + + hval = cdb_hash(key, klen); + + /* find (pos,n) hash table to use */ + /* first 2048 bytes (toc) are always available */ + /* (hval % 256) * 8 */ + htp = cdbp->cdb_mem + ((hval << 3) & 2047); /* index in toc (256x8) */ + n = cdb_unpack(htp + 4); /* table size */ + if (!n) /* empty table */ + return 0; /* not found */ + httodo = n << 3; /* bytes of htab to lookup */ + pos = cdb_unpack(htp); /* htab position */ + if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */ + || pos > cdbp->cdb_fsize /* htab start within file ? */ + || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */ + { + errno = EPROTO; + return -1; + } + + htab = cdbp->cdb_mem + pos; /* htab pointer */ + htend = htab + httodo; /* after end of htab */ + /* htab starting position: rest of hval modulo htsize, 8bytes per elt */ + htp = htab + (((hval >> 8) % n) << 3); + + for(;;) { + pos = cdb_unpack(htp + 4); /* record position */ + if (!pos) + return 0; + if (cdb_unpack(htp) == hval) { + if (pos > cdbp->cdb_fsize - 8) { /* key+val lengths */ + errno = EPROTO; + return -1; + } + if (cdb_unpack(cdbp->cdb_mem + pos) == klen) { + if (cdbp->cdb_fsize - klen < pos + 8) { + errno = EPROTO; + return -1; + } + if (memcmp(key, cdbp->cdb_mem + pos + 8, klen) == 0) { + n = cdb_unpack(cdbp->cdb_mem + pos + 4); + pos += 8 + klen; + if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) { + errno = EPROTO; + return -1; + } + cdbp->cdb_vpos = pos; + cdbp->cdb_vlen = n; + return 1; + } + } + } + httodo -= 8; + if (!httodo) + return 0; + if ((htp += 8) >= htend) + htp = htab; + } + +} + + + +/* Sequential-find routines that used separate structure. It is + possible to have many than one record with the same key in a + database, and these routines allows to enumerate all them. + cdb_findinit() initializes search structure pointed to by cdbfp. + It will return negative value on error or 0 on success. cdb_find­ + next() attempts to find next matching key, setting value position + and length in cdbfp structure. It will return positive value if + given key was found, 0 if there is no more such key(s), or negative + value on error. To access value position and length after + successeful call to cdb_findnext() (when it returned positive + result), use cdb_datapos() and cdb_datalen() macros with cdbp + pointer. It is error to use cdb_findnext() after it returned 0 or + error condition. These routines is a bit slower than + cdb_find(). + + Setting KEY to NULL will start a sequential search through the + entire DB. +*/ +int +cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp, + const void *key, cdbi_t klen) +{ + cdbi_t n, pos; + + cdbfp->cdb_cdbp = cdbp; + cdbfp->cdb_key = key; + cdbfp->cdb_klen = klen; + cdbfp->cdb_hval = key? cdb_hash(key, klen) : 0; + + if (key) + { + cdbfp->cdb_htp = cdbp->cdb_mem + ((cdbfp->cdb_hval << 3) & 2047); + n = cdb_unpack(cdbfp->cdb_htp + 4); + cdbfp->cdb_httodo = n << 3; /* Set to size of hash table. */ + if (!n) + return 0; /* The hash table is empry. */ + pos = cdb_unpack(cdbfp->cdb_htp); + if (n > (cdbp->cdb_fsize >> 3) + || pos > cdbp->cdb_fsize + || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos) + { + errno = EPROTO; + return -1; + } + + cdbfp->cdb_htab = cdbp->cdb_mem + pos; + cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo; + cdbfp->cdb_htp = cdbfp->cdb_htab + (((cdbfp->cdb_hval >> 8) % n) << 3); + } + else /* Walk over all entries. */ + { + cdbfp->cdb_hval = 0; + /* Force stepping in findnext. */ + cdbfp->cdb_htp = cdbfp->cdb_htend = cdbp->cdb_mem; + } + return 0; +} + + +/* See cdb_findinit. */ +int +cdb_findnext(struct cdb_find *cdbfp) +{ + cdbi_t pos, n; + struct cdb *cdbp = cdbfp->cdb_cdbp; + + if (cdbfp->cdb_key) + { + while(cdbfp->cdb_httodo) { + pos = cdb_unpack(cdbfp->cdb_htp + 4); + if (!pos) + return 0; + n = cdb_unpack(cdbfp->cdb_htp) == cdbfp->cdb_hval; + if ((cdbfp->cdb_htp += 8) >= cdbfp->cdb_htend) + cdbfp->cdb_htp = cdbfp->cdb_htab; + cdbfp->cdb_httodo -= 8; + if (n) { + if (pos > cdbp->cdb_fsize - 8) { + errno = EPROTO; + return -1; + } + if (cdb_unpack(cdbp->cdb_mem + pos) == cdbfp->cdb_klen) { + if (cdbp->cdb_fsize - cdbfp->cdb_klen < pos + 8) { + errno = EPROTO; + return -1; + } + if (memcmp(cdbfp->cdb_key, + cdbp->cdb_mem + pos + 8, cdbfp->cdb_klen) == 0) { + n = cdb_unpack(cdbp->cdb_mem + pos + 4); + pos += 8 + cdbfp->cdb_klen; + if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) { + errno = EPROTO; + return -1; + } + cdbp->cdb_vpos = pos; + cdbp->cdb_vlen = n; + return 1; + } + } + } + } + } + else /* Walk over all entries. */ + { + do + { + while (cdbfp->cdb_htp >= cdbfp->cdb_htend) + { + if (cdbfp->cdb_hval > 255) + return 0; /* No more items. */ + + cdbfp->cdb_htp = cdbp->cdb_mem + cdbfp->cdb_hval * 8; + cdbfp->cdb_hval++; /* Advance for next round. */ + pos = cdb_unpack (cdbfp->cdb_htp); /* Offset of table. */ + n = cdb_unpack (cdbfp->cdb_htp + 4); /* Number of entries. */ + cdbfp->cdb_httodo = n * 8; /* Size of table. */ + if (n > (cdbp->cdb_fsize / 8) + || pos > cdbp->cdb_fsize + || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos) + { + errno = EPROTO; + return -1; + } + + cdbfp->cdb_htab = cdbp->cdb_mem + pos; + cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo; + cdbfp->cdb_htp = cdbfp->cdb_htab; + } + + pos = cdb_unpack (cdbfp->cdb_htp + 4); /* Offset of record. */ + cdbfp->cdb_htp += 8; + } + while (!pos); + if (pos > cdbp->cdb_fsize - 8) + { + errno = EPROTO; + return -1; + } + + cdbp->cdb_kpos = pos + 8; + cdbp->cdb_klen = cdb_unpack(cdbp->cdb_mem + pos); + cdbp->cdb_vpos = pos + 8 + cdbp->cdb_klen; + cdbp->cdb_vlen = cdb_unpack(cdbp->cdb_mem + pos + 4); + n = 8 + cdbp->cdb_klen + cdbp->cdb_vlen; + if ( pos > cdbp->cdb_fsize || pos > cdbp->cdb_fsize - n) + { + errno = EPROTO; + return -1; + } + return 1; /* Found. */ + } + return 0; +} + +/* Read a chunk from file, ignoring interrupts (EINTR) */ +int +cdb_bread(int fd, void *buf, int len) +{ + int l; + while(len > 0) { + do l = read(fd, buf, len); + while(l < 0 && errno == EINTR); + if (l <= 0) { + if (!l) + errno = EIO; + return -1; + } + buf = (char*)buf + l; + len -= l; + } + return 0; +} + +/* Find a given key in cdb file, seek a file pointer to it's value and + place data length to *dlenp. */ +int +cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp) +{ + cdbi_t htstart; /* hash table start position */ + cdbi_t htsize; /* number of elements in a hash table */ + cdbi_t httodo; /* hash table elements left to look */ + cdbi_t hti; /* hash table index */ + cdbi_t pos; /* position in a file */ + cdbi_t hval; /* key's hash value */ + unsigned char rbuf[64]; /* read buffer */ + int needseek = 1; /* if we should seek to a hash slot */ + + hval = cdb_hash(key, klen); + pos = (hval & 0xff) << 3; /* position in TOC */ + /* read the hash table parameters */ + if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) + return -1; + if ((htsize = cdb_unpack(rbuf + 4)) == 0) + return 0; + hti = (hval >> 8) % htsize; /* start position in hash table */ + httodo = htsize; + htstart = cdb_unpack(rbuf); + + for(;;) { + if (needseek && lseek(fd, htstart + (hti << 3), SEEK_SET) < 0) + return -1; + if (cdb_bread(fd, rbuf, 8) < 0) + return -1; + if ((pos = cdb_unpack(rbuf + 4)) == 0) /* not found */ + return 0; + + if (cdb_unpack(rbuf) != hval) /* hash value not matched */ + needseek = 0; + else { /* hash value matched */ + if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) + return -1; + if (cdb_unpack(rbuf) == klen) { /* key length matches */ + /* read the key from file and compare with wanted */ + cdbi_t l = klen, c; + const char *k = (const char*)key; + if (*dlenp) + *dlenp = cdb_unpack(rbuf + 4); /* save value length */ + for(;;) { + if (!l) /* the whole key read and matches, return */ + return 1; + c = l > sizeof(rbuf) ? sizeof(rbuf) : l; + if (cdb_bread(fd, rbuf, c) < 0) + return -1; + if (memcmp(rbuf, k, c) != 0) /* no, it differs, stop here */ + break; + k += c; l -= c; + } + } + needseek = 1; /* we're looked to other place, should seek back */ + } + if (!--httodo) + return 0; + if (++hti == htsize) { + hti = htstart; + needseek = 1; + } + } +} + +cdbi_t +cdb_unpack(const unsigned char buf[4]) +{ + cdbi_t n = buf[3]; + n <<= 8; n |= buf[2]; + n <<= 8; n |= buf[1]; + n <<= 8; n |= buf[0]; + return n; +} + +/* Add record with key (KEY,KLEN) and value (VAL,VLEN) to a database. + Returns 0 on success or negative value on error. Note that this + routine does not checks if given key already exists, but cdb_find() + will not see second record with the same key. It is not possible + to continue building a database if cdb_make_add() returned an error + indicator. */ +int +cdb_make_add(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen) +{ + unsigned char rlen[8]; + cdbi_t hval; + struct cdb_rl *rl; + if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || + vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) { + errno = ENOMEM; + return -1; + } + hval = cdb_hash(key, klen); + rl = cdbmp->cdb_rec[hval&255]; + if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) { + rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl)); + if (!rl) { + errno = ENOMEM; + return -1; + } + rl->cnt = 0; + rl->next = cdbmp->cdb_rec[hval&255]; + cdbmp->cdb_rec[hval&255] = rl; + } + rl->rec[rl->cnt].hval = hval; + rl->rec[rl->cnt].rpos = cdbmp->cdb_dpos; + ++rl->cnt; + ++cdbmp->cdb_rcnt; + cdb_pack(klen, rlen); + cdb_pack(vlen, rlen + 4); + if (make_write(cdbmp, rlen, 8) < 0 || + make_write(cdbmp, key, klen) < 0 || + make_write(cdbmp, val, vlen) < 0) + return -1; + return 0; +} + +int +cdb_make_put(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen, + int flags) +{ + unsigned char rlen[8]; + cdbi_t hval = cdb_hash(key, klen); + struct cdb_rl *rl; + int c, r; + + switch(flags) { + case CDB_PUT_REPLACE: + case CDB_PUT_INSERT: + case CDB_PUT_WARN: + c = make_find(cdbmp, key, klen, hval, &rl); + if (c < 0) + return -1; + if (c) { + if (flags == CDB_PUT_INSERT) { + errno = EEXIST; + return 1; + } + else if (flags == CDB_PUT_REPLACE) { + --c; + r = 1; + break; + } + else + r = 1; + } + /* fall */ + + case CDB_PUT_ADD: + rl = cdbmp->cdb_rec[hval&255]; + if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) { + rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl)); + if (!rl) { + errno = ENOMEM; + return -1; + } + rl->cnt = 0; + rl->next = cdbmp->cdb_rec[hval&255]; + cdbmp->cdb_rec[hval&255] = rl; + } + c = rl->cnt; + r = 0; + break; + + default: + errno = EINVAL; + return -1; + } + + if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || + vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) { + errno = ENOMEM; + return -1; + } + rl->rec[c].hval = hval; + rl->rec[c].rpos = cdbmp->cdb_dpos; + if (c == rl->cnt) { + ++rl->cnt; + ++cdbmp->cdb_rcnt; + } + cdb_pack(klen, rlen); + cdb_pack(vlen, rlen + 4); + if (make_write(cdbmp, rlen, 8) < 0 || + make_write(cdbmp, key, klen) < 0 || + make_write(cdbmp, val, vlen) < 0) + return -1; + return r; +} + + +static int +match(int fd, cdbi_t pos, const char *key, cdbi_t klen) +{ + unsigned char buf[64]; /*XXX cdb_buf may be used here instead */ + if (lseek(fd, pos, SEEK_SET) < 0 || read(fd, buf, 8) != 8) + return -1; + if (cdb_unpack(buf) != klen) + return 0; + + while(klen > sizeof(buf)) { + if (read(fd, buf, sizeof(buf)) != sizeof(buf)) + return -1; + if (memcmp(buf, key, sizeof(buf)) != 0) + return 0; + key += sizeof(buf); + klen -= sizeof(buf); + } + if (klen) { + if (read(fd, buf, klen) != klen) + return -1; + if (memcmp(buf, key, klen) != 0) + return 0; + } + return 1; +} + + +static int +make_find (struct cdb_make *cdbmp, + const void *key, cdbi_t klen, cdbi_t hval, + struct cdb_rl **rlp) +{ + struct cdb_rl *rl = cdbmp->cdb_rec[hval&255]; + int r, i; + int seeked = 0; + while(rl) { + for(i = rl->cnt - 1; i >= 0; --i) { /* search backward */ + if (rl->rec[i].hval != hval) + continue; + /*XXX this explicit flush may be unnecessary having + * smarter match() that looks to cdb_buf too, but + * most of a time here spent in finding hash values + * (above), not keys */ + if (cdbmp->cdb_bpos != cdbmp->cdb_buf) { + if (write(cdbmp->cdb_fd, cdbmp->cdb_buf, + cdbmp->cdb_bpos - cdbmp->cdb_buf) < 0) + return -1; + cdbmp->cdb_bpos = cdbmp->cdb_buf; + } + seeked = 1; + r = match(cdbmp->cdb_fd, rl->rec[i].rpos, key, klen); + if (!r) + continue; + if (r < 0) + return -1; + if (lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0) + return -1; + if (rlp) + *rlp = rl; + return i + 1; + } + rl = rl->next; + } + if (seeked && lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0) + return -1; + return 0; +} + +int +cdb_make_exists(struct cdb_make *cdbmp, + const void *key, cdbi_t klen) +{ + return make_find(cdbmp, key, klen, cdb_hash(key, klen), NULL); +} + + +void +cdb_pack(cdbi_t num, unsigned char buf[4]) +{ + buf[0] = num & 255; num >>= 8; + buf[1] = num & 255; num >>= 8; + buf[2] = num & 255; + buf[3] = num >> 8; +} + + +/* Initializes structure to create a database. File FD should be + opened read-write and should be seekable. Returns 0 on success or + negative value on error. */ +int +cdb_make_start(struct cdb_make *cdbmp, int fd) +{ + memset (cdbmp, 0, sizeof *cdbmp); + cdbmp->cdb_fd = fd; + cdbmp->cdb_dpos = 2048; + cdbmp->cdb_bpos = cdbmp->cdb_buf + 2048; + return 0; +} + + +static int +ewrite(int fd, const char *buf, int len) +{ + while(len) { + int l = write(fd, buf, len); + if (l < 0 && errno != EINTR) + return -1; + if (l > 0) + { + len -= l; + buf += l; + } + } + return 0; +} + +static int +make_write(struct cdb_make *cdbmp, const char *ptr, cdbi_t len) +{ + cdbi_t l = sizeof(cdbmp->cdb_buf) - (cdbmp->cdb_bpos - cdbmp->cdb_buf); + cdbmp->cdb_dpos += len; + if (len > l) { + memcpy(cdbmp->cdb_bpos, ptr, l); + if (ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, sizeof(cdbmp->cdb_buf)) < 0) + return -1; + ptr += l; len -= l; + l = len / sizeof(cdbmp->cdb_buf); + if (l) { + l *= sizeof(cdbmp->cdb_buf); + if (ewrite(cdbmp->cdb_fd, ptr, l) < 0) + return -1; + ptr += l; len -= l; + } + cdbmp->cdb_bpos = cdbmp->cdb_buf; + } + if (len) { + memcpy(cdbmp->cdb_bpos, ptr, len); + cdbmp->cdb_bpos += len; + } + return 0; +} + +static int +cdb_make_finish_internal(struct cdb_make *cdbmp) +{ + cdbi_t hcnt[256]; /* hash table counts */ + cdbi_t hpos[256]; /* hash table positions */ + struct cdb_rec *htab; + unsigned char *p; + struct cdb_rl *rl; + cdbi_t hsize; + unsigned t, i; + + if (((0xffffffff - cdbmp->cdb_dpos) >> 3) < cdbmp->cdb_rcnt) { + errno = ENOMEM; + return -1; + } + + /* count htab sizes and reorder reclists */ + hsize = 0; + for (t = 0; t < 256; ++t) { + struct cdb_rl *rlt = NULL; + i = 0; + rl = cdbmp->cdb_rec[t]; + while(rl) { + struct cdb_rl *rln = rl->next; + rl->next = rlt; + rlt = rl; + i += rl->cnt; + rl = rln; + } + cdbmp->cdb_rec[t] = rlt; + if (hsize < (hcnt[t] = i << 1)) + hsize = hcnt[t]; + } + + /* allocate memory to hold max htable */ + htab = (struct cdb_rec*)malloc((hsize + 2) * sizeof(struct cdb_rec)); + if (!htab) { + errno = ENOENT; + return -1; + } + p = (unsigned char *)htab; + htab += 2; + + /* build hash tables */ + for (t = 0; t < 256; ++t) { + cdbi_t len, hi; + hpos[t] = cdbmp->cdb_dpos; + if ((len = hcnt[t]) == 0) + continue; + for (i = 0; i < len; ++i) + htab[i].hval = htab[i].rpos = 0; + for (rl = cdbmp->cdb_rec[t]; rl; rl = rl->next) + for (i = 0; i < rl->cnt; ++i) { + hi = (rl->rec[i].hval >> 8) % len; + while(htab[hi].rpos) + if (++hi == len) + hi = 0; + htab[hi] = rl->rec[i]; + } + for (i = 0; i < len; ++i) { + cdb_pack(htab[i].hval, p + (i << 3)); + cdb_pack(htab[i].rpos, p + (i << 3) + 4); + } + if (make_write(cdbmp, p, len << 3) < 0) { + free(p); + return -1; + } + } + free(p); + if (cdbmp->cdb_bpos != cdbmp->cdb_buf && + ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, + cdbmp->cdb_bpos - cdbmp->cdb_buf) != 0) + return -1; + p = cdbmp->cdb_buf; + for (t = 0; t < 256; ++t) { + cdb_pack(hpos[t], p + (t << 3)); + cdb_pack(hcnt[t], p + (t << 3) + 4); + } + if (lseek(cdbmp->cdb_fd, 0, 0) != 0 || + ewrite(cdbmp->cdb_fd, p, 2048) != 0) + return -1; + + return 0; +} + +static void +cdb_make_free(struct cdb_make *cdbmp) +{ + unsigned t; + for(t = 0; t < 256; ++t) { + struct cdb_rl *rl = cdbmp->cdb_rec[t]; + while(rl) { + struct cdb_rl *tm = rl; + rl = rl->next; + free(tm); + } + } +} + + + +/* Finalizes database file, constructing all needed indexes, and frees + memory structures. It does not close the file descriptor. Returns + 0 on success or a negative value on error. */ +int +cdb_make_finish(struct cdb_make *cdbmp) +{ + int r = cdb_make_finish_internal(cdbmp); + cdb_make_free(cdbmp); + return r; +} + + +cdbi_t +cdb_hash(const void *buf, cdbi_t len) +{ + register const unsigned char *p = (const unsigned char *)buf; + register const unsigned char *end = p + len; + register cdbi_t hash = 5381; /* start value */ + while (p < end) + hash = (hash + (hash << 5)) ^ *p++; + return hash; +} diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c new file mode 100644 index 000000000..c40bb17d0 --- /dev/null +++ b/dirmngr/certcache.c @@ -0,0 +1,1384 @@ +/* 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 looking is not + needed but it would take extra efforts to make sure that no + indirect use of pth 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 pth_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) +{ + if (!pth_rwlock_init (&cert_cache_lock)) + log_fatal (_("can't initialize certificate cache lock: %s\n"), + strerror (errno)); +} + +static void +acquire_cache_read_lock (void) +{ + if (!pth_rwlock_acquire (&cert_cache_lock, PTH_RWLOCK_RD, FALSE, NULL)) + log_fatal (_("can't acquire read lock on the certificate cache: %s\n"), + strerror (errno)); +} + +static void +acquire_cache_write_lock (void) +{ + if (!pth_rwlock_acquire (&cert_cache_lock, PTH_RWLOCK_RW, FALSE, NULL)) + log_fatal (_("can't acquire write lock on the certificate cache: %s\n"), + strerror (errno)); +} + +static void +release_cache_lock (void) +{ + if (!pth_rwlock_release (&cert_cache_lock)) + log_fatal (_("can't release lock on the certificate cache: %s\n"), + strerror (errno)); +} + + +/* 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 serialnumber + 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 fucntion. */ +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; + FILE *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 = fopen (fname, "rb"); + if (!fp) + { + log_error (_("can't open `%s': %s\n"), + fname, strerror (errno)); + continue; + } + err = ksba_reader_new (&reader); + if (!err) + err = ksba_reader_set_file (reader, fp); + if (err) + { + log_error (_("can't setup KSBA reader: %s\n"), gpg_strerror (err)); + ksba_reader_release (reader); + fclose (fp); + continue; + } + + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_read_der (cert, reader); + ksba_reader_release (reader); + 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 (opt.homedir, "trusted-certs", NULL); + load_certs_from_dir (dname, 1); + xfree (dname); + + dname = make_filename (opt.homedir_data, "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; + + 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 decribing 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 = PATTERN_UNKNOWN; + const char *s; + int hexprefix = 0; + int hexlength; + int mode = 0; + + *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. */ + mode = 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) + { + 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/certcache.h b/dirmngr/certcache.h new file mode 100644 index 000000000..2b7aeaf74 --- /dev/null +++ b/dirmngr/certcache.h @@ -0,0 +1,103 @@ +/* certcache.h - Certificate caching + * Copyright (C) 2004, 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 + */ + +#ifndef CERTCACHE_H +#define CERTCACHE_H + +/* First time initialization of the certificate cache. */ +void cert_cache_init (void); + +/* Deinitialize the certificate cache. */ +void cert_cache_deinit (int full); + +/* Print some statistics to the log file. */ +void cert_cache_print_stats (void); + +/* 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); + +/* Put CERT into the certificate cache. */ +gpg_error_t cache_cert (ksba_cert_t cert); + +/* Put CERT into the certificate cache and return the fingerprint. */ +gpg_error_t cache_cert_silent (ksba_cert_t cert, void *fpr_buffer); + +/* 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); + + +/* 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. */ +ksba_cert_t get_cert_byfpr (const unsigned char *fpr); + +/* 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. */ +ksba_cert_t get_cert_byhexfpr (const char *string); + +/* Return the certificate matching ISSUER_DN and SERIALNO. */ +ksba_cert_t get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno); + +/* 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); + +/* Return the certificate matching SUBJECT_DN. SEQ should initially be + set to 0 and bumped up to get the next issuer with that DN. */ +ksba_cert_t get_cert_bysubject (const char *subject_dn, unsigned int seq); + +/* 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); + +/* 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); + + +/* 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); + +/* 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); + + + + +#endif /*CERTCACHE_H*/ diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c new file mode 100644 index 000000000..9ec5414fa --- /dev/null +++ b/dirmngr/crlcache.c @@ -0,0 +1,2544 @@ +/* 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" +#include "estream-printf.h" + +/* Change this whenever the format changes */ +#define DBDIR_D (opt.system_daemon? "crls.d" : "dirmngr-cache.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 isi 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); + 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 (unlink (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 (FILE *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=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 FILE * +open_dir_file (const char *fname) +{ + FILE *fp; + + fp = 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 = fopen (fname, "w"); + if (!fp) + { + log_error (_("error creating new cache dir file `%s': %s\n"), + fname, strerror (errno)); + return NULL; + } + fprintf (fp, "v:%d:\n", DBDIRVERSION); + if (ferror (fp)) + { + log_error (_("error writing new cache dir file `%s': %s\n"), + fname, strerror (errno)); + fclose (fp); + return NULL; + } + if (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 = 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 (FILE **fpadr, const char *fname, + unsigned int *lineno, + int cleanup_on_mismatch) +{ + char *line; + gpg_error_t lineerr = 0; + FILE *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; + + if (strtol (line+2, NULL, 10) != DBDIRVERSION) + { + if (!created && cleanup_on_mismatch) + { + log_error (_("old version of cache directory - cleaning up\n")); + 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; + FILE *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); break; + case 6: strncpy (entry->next_update, p, 15); 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 (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: + if (fp) + 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, FILE *fp) +{ + for (; *s; s++) + if (*s == ':') + fputs ("%3A", fp); + else if (*s == '\n') + fputs ("%0A", fp); + else if (*s == '\r') + fputs ("%0D", fp); + else + putc (*s, fp); +} + + +static void +write_dir_line_crl (FILE *fp, crl_cache_entry_t e) +{ + if (e->invalid) + fprintf (fp, "i%d", e->invalid); + else if (e->user_trust_req) + putc ('u', fp); + else + putc ('c', fp); + putc (':', fp); + fputs (e->issuer_hash, fp); + putc (':', fp); + write_percented_string (e->issuer, fp); + putc (':', fp); + write_percented_string (e->url, fp); + putc (':', fp); + fwrite (e->this_update, 15, 1, fp); + putc (':', fp); + fwrite (e->next_update, 15, 1, fp); + putc (':', fp); + fputs (e->dbfile_hash, fp); + putc (':', fp); + if (e->crl_number) + fputs (e->crl_number, fp); + putc (':', fp); + if (e->authority_issuer) + write_percented_string (e->authority_issuer, fp); + putc (':', fp); + if (e->authority_serialno) + fputs (e->authority_serialno, fp); + putc (':', fp); + if (e->check_trust_anchor && e->user_trust_req) + fputs (e->check_trust_anchor, fp); + 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; + FILE *fp, *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 = 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; + 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 + + estream_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 = 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 orginal 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. */ + fprintf (fpout, "# Next line added by " + "another process; our pid is %lu\n", + (unsigned long)getpid ()); + fputs (line, fpout); + putc ('\n', fpout); + } + } + else + { + fputs ("# Invalid line detected: ", fpout); + fputs (line, fpout); + putc ('\n', fpout); + } + } + else + { + /* Write out all non CRL lines as they are. */ + fputs (line, fpout); + putc ('\n', fpout); + } + + xfree (line); + } + if (!ferror (fp) && !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 (ferror (fp)) + { + err = gpg_error_from_errno (errno); + log_error (_("error reading `%s': %s\n"), fname, strerror (errno)); + } + if (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. */ + fclose (fp); + fp = NULL; + if (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. */ + unlink (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); + if (fp) + fclose (fp); + xfree (fname); + if (fpout) + { + fclose (fpout); + if (err && tmpfname) + 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) +{ + FILE *fp; + char *buffer; + size_t n; + gcry_md_hd_t md5; + gpg_err_code_t err; + + buffer = xtrymalloc (65536); + fp = buffer? 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); + 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 = fread (buffer, 1, 65536, fp); + if (n < 65536 && ferror (fp)) + { + log_error (_("error hashing `%s': %s\n"), fname, strerror (errno)); + xfree (buffer); + fclose (fp); + gcry_md_close (md5); + return -1; + } + if (!n) + break; + gcry_md_write (md5, buffer, n); + } + 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; + } +} + + +/* 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 fucntion 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_printhex ("", sn, snlen); + } + else if (opt.verbose) + { + unsigned char record[16]; + char *tmp = hexify_data (sn, snlen); + + 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); + 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); + 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: + { + if (start_sig_check (crl, &md, &algo )) + 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. */ +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 + + estream_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 (!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); + } + + + /* 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); +#ifdef HAVE_W32_SYSTEM + unlink (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) + { + 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, FILE *fp) +{ + struct cdb_find cdbfp; + struct cdb *cdb; + int rc; + int warn = 0; + const unsigned char *s; + + fputs ("--------------------------------------------------------\n", fp ); + fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); + fprintf (fp, " Issuer:\t%s\n", e->issuer ); + fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash ); + fprintf (fp, " This Update:\t%s\n", e->this_update ); + fprintf (fp, " Next Update:\t%s\n", e->next_update ); + fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none"); + fprintf (fp, " AuthKeyId :\t%s\n", + e->authority_serialno? e->authority_serialno:"none"); + if (e->authority_serialno && e->authority_issuer) + { + fputs (" \t", fp); + for (s=e->authority_issuer; *s; s++) + if (*s == '\x01') + fputs ("\n \t", fp); + else + putc (*s, fp); + putc ('\n', fp); + } + 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)) + fprintf (fp, _(" ERROR: The CRL will not be used because it was still too old after an update!\n")); + if ((e->invalid & 2)) + fprintf (fp, _(" ERROR: The CRL will not be used due to an unknown critical extension!\n")); + if ((e->invalid & ~3)) + 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) + fprintf (fp, _(" ERROR: This cached CRL may has been tampered with!\n")); + + 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; + fputs (" ", fp); + for (i = 0; i < n; i++) + fprintf (fp, "%02X", keyrecord[i]); + fputs (":\t reasons( ", fp); + + if (reason & KSBA_CRLREASON_UNSPECIFIED) + fputs( "unspecified ", fp ), any = 1; + if (reason & KSBA_CRLREASON_KEY_COMPROMISE ) + fputs( "key_compromise ", fp ), any = 1; + if (reason & KSBA_CRLREASON_CA_COMPROMISE ) + fputs( "ca_compromise ", fp ), any = 1; + if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED ) + fputs( "affiliation_changed ", fp ), any = 1; + if (reason & KSBA_CRLREASON_SUPERSEDED ) + fputs( "superseeded", fp ), any = 1; + if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION ) + fputs( "cessation_of_operation", fp ), any = 1; + if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD ) + fputs( "certificate_hold", fp ), any = 1; + if (reason && !any) + fputs( "other", fp ); + + 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); + fprintf (fp, _("End CRL dump\n") ); + 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 (FILE *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; + FILE *fp; + ksba_reader_t reader; + + fp = 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 = ksba_reader_new (&reader); + if (!err) + err = ksba_reader_set_file (reader, fp); + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + ksba_reader_release (reader); + return err; + } + err = crl_cache_insert (ctrl, filename, reader); + ksba_reader_release (reader); + 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; + + } + 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"); + + if (reader) + { + 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: + if (reader) + 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/crlcache.h b/dirmngr/crlcache.h new file mode 100644 index 000000000..b9e487436 --- /dev/null +++ b/dirmngr/crlcache.h @@ -0,0 +1,70 @@ +/* crlcache.h - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * 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 + */ + +#ifndef CRLCACHE_H +#define CRLCACHE_H + + +typedef enum + { + CRL_CACHE_VALID = 0, + CRL_CACHE_INVALID, + CRL_CACHE_DONTKNOW, + CRL_CACHE_CANTUSE + } +crl_cache_result_t; + +typedef enum foo + { + CRL_SIG_OK = 0, + CRL_SIG_NOT_OK, + CRL_TOO_OLD, + CRL_SIG_ERROR, + CRL_GENERAL_ERROR + } +crl_sig_result_t; + +struct crl_cache_entry_s; +typedef struct crl_cache_entry_s *crl_cache_entry_t; + + +void crl_cache_init (void); +void crl_cache_deinit (void); +int crl_cache_flush(void); + +crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl, + const char *issuer_hash, + const char *cert_id, + int force_refresh); + +gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, + int force_refresh); + +gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url, + ksba_reader_t reader); + +gpg_error_t crl_cache_list (FILE* fp); + +gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename); + +gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert); + + +#endif /* CRLCACHE_H */ diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c new file mode 100644 index 000000000..ca6c77a84 --- /dev/null +++ b/dirmngr/crlfetch.c @@ -0,0 +1,479 @@ +/* crlfetch.c - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2005, 2006, 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, see . + */ + +#include + +#include +#include +#include + +#include "crlfetch.h" +#include "dirmngr.h" +#include "misc.h" +#include "http.h" + +#include "estream.h" + + +/* For detecting armored CRLs received via HTTP (yes, such CRLS really + exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June + 2008) we need a context in the reader callback. */ +struct reader_cb_context_s +{ + estream_t fp; /* The stream used with the ksba reader. */ + int checked:1; /* PEM/binary detection ahs been done. */ + int is_pem:1; /* The file stream is PEM encoded. */ + struct b64state b64state; /* The state used for Base64 decoding. */ +}; + + +/* We need to associate a reader object with the reader callback + context. This table is used for it. */ +struct file_reader_map_s +{ + ksba_reader_t reader; + struct reader_cb_context_s *cb_ctx; +}; +#define MAX_FILE_READER 50 +static struct file_reader_map_s file_reader_map[MAX_FILE_READER]; + +/* Associate FP with READER. If the table is full wait until another + thread has removed an entry. */ +static void +register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx) +{ + int i; + + for (;;) + { + for (i=0; i < MAX_FILE_READER; i++) + if (!file_reader_map[i].reader) + { + file_reader_map[i].reader = reader; + file_reader_map[i].cb_ctx = cb_ctx; + return; + } + log_info (_("reader to file mapping table full - waiting\n")); + pth_sleep (2); + } +} + +/* Scan the table for an entry matching READER, remove that entry and + return the associated file pointer. */ +static struct reader_cb_context_s * +get_file_reader (ksba_reader_t reader) +{ + struct reader_cb_context_s *cb_ctx = NULL; + int i; + + for (i=0; i < MAX_FILE_READER; i++) + if (file_reader_map[i].reader == reader) + { + cb_ctx = file_reader_map[i].cb_ctx; + file_reader_map[i].reader = NULL; + file_reader_map[i].cb_ctx = NULL; + break; + } + return cb_ctx; +} + + + +static int +my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) +{ + struct reader_cb_context_s *cb_ctx = opaque; + int result; + + result = es_read (cb_ctx->fp, buffer, nbytes, nread); + if (result) + return result; + /* Fixme we should check whether the semantics of es_read are okay + and well defined. I have some doubts. */ + if (nbytes && !*nread && es_feof (cb_ctx->fp)) + return gpg_error (GPG_ERR_EOF); + if (!nread && es_ferror (cb_ctx->fp)) + return gpg_error (GPG_ERR_EIO); + + if (!cb_ctx->checked && *nread) + { + int c = *(unsigned char *)buffer; + + cb_ctx->checked = 1; + if ( ((c & 0xc0) >> 6) == 0 /* class: universal */ + && (c & 0x1f) == 16 /* sequence */ + && (c & 0x20) /* is constructed */ ) + ; /* Binary data. */ + else + { + cb_ctx->is_pem = 1; + b64dec_start (&cb_ctx->b64state, ""); + } + } + if (cb_ctx->is_pem && *nread) + { + size_t nread2; + + if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) + { + /* EOF from decoder. */ + *nread = 0; + result = gpg_error (GPG_ERR_EOF); + } + else + *nread = nread2; + } + + return result; +} + + +/* Fetch CRL from URL and return the entire CRL using new ksba reader + object in READER. Note that this reader object should be closed + only using ldap_close_reader. */ +gpg_error_t +crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) +{ + gpg_error_t err; + parsed_uri_t uri; + char *free_this = NULL; + int redirects_left = 2; /* We allow for 2 redirect levels. */ + + *reader = NULL; + + once_more: + err = http_parse_uri (&uri, url); + http_release_parsed_uri (uri); + if (err && url && !strncmp (url, "https:", 6)) + { + /* Our HTTP code does not support TLS, thus we can't use this + scheme and it is frankly not useful for CRL retrieval anyway. + We resort to using http, assuming that the server also + provides plain http access. */ + free_this = xtrymalloc (strlen (url) + 1); + if (free_this) + { + strcpy (stpcpy (free_this,"http:"), url+6); + err = http_parse_uri (&uri, free_this); + http_release_parsed_uri (uri); + if (!err) + { + log_info (_("using \"http\" instead of \"https\"\n")); + url = free_this; + } + } + } + if (!err) /* Yes, our HTTP code groks that. */ + { + http_t hd; + + if (opt.disable_http) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "HTTP"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + err = http_open_document (&hd, url, NULL, + (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) + |HTTP_FLAG_NEED_HEADER + |(DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0), + opt.http_proxy, NULL); + + switch ( err? 99999 : http_get_status_code (hd) ) + { + case 200: + { + estream_t fp = http_get_read_ptr (hd); + struct reader_cb_context_s *cb_ctx; + + cb_ctx = xtrycalloc (1, sizeof *cb_ctx); + if (!cb_ctx) + err = gpg_error_from_syserror (); + if (!err) + err = ksba_reader_new (reader); + if (!err) + { + cb_ctx->fp = fp; + err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx); + } + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + ksba_reader_release (*reader); + *reader = NULL; + http_close (hd, 0); + } + else + { + /* The ksba reader misses a user pointer thus we need + to come up with our own way of associating a file + pointer (or well the callback context) with the + reader. It is only required when closing the + reader thus there is no performance issue doing it + this way. */ + register_file_reader (*reader, cb_ctx); + http_close (hd, 1); + } + } + break; + + case 301: /* Redirection (perm.). */ + case 302: /* Redirection (temp.). */ + { + const char *s = http_get_header (hd, "Location"); + + log_info (_("URL `%s' redirected to `%s' (%u)\n"), + url, s?s:"[none]", http_get_status_code (hd)); + 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 (hd, 0); + /* Note, that our implementation of redirection + actually handles a redirect to LDAP. */ + goto once_more; + } + } + else + err = gpg_error (GPG_ERR_NO_DATA); + log_error (_("too many redirections\n")); /* Or no "Location". */ + http_close (hd, 0); + } + break; + + case 99999: /* Made up status code foer error reporting. */ + log_error (_("error retrieving `%s': %s\n"), + url, gpg_strerror (err)); + break; + + default: + log_error (_("error retrieving `%s': http status %u\n"), + url, http_get_status_code (hd)); + err = gpg_error (GPG_ERR_NO_DATA); + http_close (hd, 0); + } + } + else /* Let the LDAP code try other schemes. */ + { + if (opt.disable_ldap) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "LDAP"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + err = url_fetch_ldap (ctrl, url, NULL, 0, reader); + } + + xfree (free_this); + return err; +} + + +/* Fetch CRL for ISSUER using a default server. Return the entire CRL + as a newly opened stream returned in R_FP. */ +gpg_error_t +crl_fetch_default (ctrl_t ctrl, const char *issuer, ksba_reader_t *reader) +{ + if (opt.disable_ldap) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "LDAP"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList", + reader); +} + + +/* Fetch a CA certificate for DN using the default server. This + function only initiates the fetch; fetch_next_cert must be used to + actually read the certificate; end_cert_fetch to end the + operation. */ +gpg_error_t +ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn) +{ + if (opt.disable_ldap) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "LDAP"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + return start_default_fetch_ldap (ctrl, context, dn, "cACertificate"); +} + + +gpg_error_t +start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, + strlist_t patterns, const ldap_server_t server) +{ + if (opt.disable_ldap) + { + log_error (_("certificate search not possible due to disabled %s\n"), + "LDAP"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + return start_cert_fetch_ldap (ctrl, context, patterns, server); +} + + +gpg_error_t +fetch_next_cert (cert_fetch_context_t context, + unsigned char **value, size_t * valuelen) +{ + return fetch_next_cert_ldap (context, value, valuelen); +} + + +/* Fetch the next data from CONTEXT, assuming it is a certificate and return + it as a cert object in R_CERT. */ +gpg_error_t +fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert) +{ + gpg_error_t err; + unsigned char *value; + size_t valuelen; + ksba_cert_t cert; + + *r_cert = NULL; + + err = fetch_next_cert_ldap (context, &value, &valuelen); + if (!err && !value) + err = gpg_error (GPG_ERR_BUG); + if (err) + return err; + + err = ksba_cert_new (&cert); + if (err) + { + xfree (value); + return err; + } + + err = ksba_cert_init_from_mem (cert, value, valuelen); + xfree (value); + if (err) + { + ksba_cert_release (cert); + return err; + } + *r_cert = cert; + return 0; +} + + +void +end_cert_fetch (cert_fetch_context_t context) +{ + return end_cert_fetch_ldap (context); +} + + +/* Lookup a cert by it's URL. */ +gpg_error_t +fetch_cert_by_url (ctrl_t ctrl, const char *url, + unsigned char **value, size_t *valuelen) +{ + const unsigned char *cert_image; + size_t cert_image_n; + ksba_reader_t reader; + ksba_cert_t cert; + gpg_error_t err; + + *value = NULL; + *valuelen = 0; + cert_image = NULL; + reader = NULL; + cert = NULL; + + err = url_fetch_ldap (ctrl, url, NULL, 0, &reader); + if (err) + goto leave; + + err = ksba_cert_new (&cert); + if (err) + goto leave; + + err = ksba_cert_read_der (cert, reader); + if (err) + goto leave; + + cert_image = ksba_cert_get_image (cert, &cert_image_n); + if (!cert_image || !cert_image_n) + { + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + + *value = xtrymalloc (cert_image_n); + if (!*value) + { + err = gpg_error_from_syserror (); + goto leave; + } + + memcpy (*value, cert_image, cert_image_n); + *valuelen = cert_image_n; + + leave: + + ksba_cert_release (cert); + ldap_wrapper_release_context (reader); + + return err; +} + +/* This function is to be used to close the reader object. In + addition to running ksba_reader_release it also releases the LDAP + or HTTP contexts associated with that reader. */ +void +crl_close_reader (ksba_reader_t reader) +{ + struct reader_cb_context_s *cb_ctx; + + if (!reader) + return; + + /* Check whether this is a HTTP one. */ + cb_ctx = get_file_reader (reader); + if (cb_ctx) + { + /* This is an HTTP context. */ + if (cb_ctx->fp) + es_fclose (cb_ctx->fp); + /* Release the base64 decoder state. */ + if (cb_ctx->is_pem) + b64dec_finish (&cb_ctx->b64state); + /* Release the callback context. */ + xfree (cb_ctx); + } + else /* This is an ldap wrapper context (Currently not used). */ + ldap_wrapper_release_context (reader); + + /* Now get rid of the reader object. */ + ksba_reader_release (reader); +} diff --git a/dirmngr/crlfetch.h b/dirmngr/crlfetch.h new file mode 100644 index 000000000..e42196dc7 --- /dev/null +++ b/dirmngr/crlfetch.h @@ -0,0 +1,93 @@ +/* crlfetch.h - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * 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 . + */ + +#ifndef CRLFETCH_H +#define CRLFETCH_H + +#include "dirmngr.h" + + +struct cert_fetch_context_s; +typedef struct cert_fetch_context_s *cert_fetch_context_t; + + +/* Fetch CRL from URL. */ +gpg_error_t crl_fetch (ctrl_t ctrl, const char* url, ksba_reader_t *reader); + +/* Fetch CRL for ISSUER using default server. */ +gpg_error_t crl_fetch_default (ctrl_t ctrl, + const char* issuer, ksba_reader_t *reader); + + +/* Fetch cert for DN. */ +gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, + const char *dn); + + +/* Query the server for certs matching patterns. */ +gpg_error_t start_cert_fetch (ctrl_t ctrl, + cert_fetch_context_t *context, + strlist_t patterns, + const ldap_server_t server); +gpg_error_t fetch_next_cert(cert_fetch_context_t context, + unsigned char **value, size_t *valuelen); +gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, + ksba_cert_t *r_cert); +void end_cert_fetch (cert_fetch_context_t context); + +/* Lookup a cert by it's URL. */ +gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, + unsigned char **value, size_t *valuelen); + +/* Close a reader object. */ +void crl_close_reader (ksba_reader_t reader); + + + +/*-- ldap.c --*/ +void *ldap_wrapper_thread (void*); +void ldap_wrapper_wait_connections (void); +void ldap_wrapper_release_context (ksba_reader_t reader); +void ldap_wrapper_connection_cleanup (ctrl_t); + +gpg_error_t url_fetch_ldap (ctrl_t ctrl, + const char *url, const char *host, int port, + ksba_reader_t *reader); +gpg_error_t attr_fetch_ldap (ctrl_t ctrl, + const char *dn, const char *attr, + ksba_reader_t *reader); + + +gpg_error_t start_default_fetch_ldap (ctrl_t ctrl, + cert_fetch_context_t *context, + const char *dn, const char *attr); +gpg_error_t start_cert_fetch_ldap( ctrl_t ctrl, + cert_fetch_context_t *context, + strlist_t patterns, + const ldap_server_t server ); +gpg_error_t fetch_next_cert_ldap (cert_fetch_context_t context, + unsigned char **value, size_t *valuelen ); +void end_cert_fetch_ldap (cert_fetch_context_t context); + + + + + + +#endif /* CRLFETCH_H */ diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c new file mode 100644 index 000000000..1e388408d --- /dev/null +++ b/dirmngr/dirmngr-client.c @@ -0,0 +1,1042 @@ +/* dirmngr-client.c - A client for the dirmngr daemon + * Copyright (C) 2004, 2007 g10 Code GmbH + * Copyright (C) 2002, 2003 Free Software Foundation, Inc. + * + * 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 +#include + +#define JNLIB_NEED_LOG_LOGV +#include "../common/logging.h" +#include "../common/argparse.h" +#include "../common/stringhelp.h" +#include "../common/mischelp.h" +#include "../common/strlist.h" + +#include "i18n.h" +#include "util.h" + + +/* Constants for the options. */ +enum + { + oQuiet = 'q', + oVerbose = 'v', + oLocal = 'l', + oUrl = 'u', + + oOCSP = 500, + oPing, + oCacheCert, + oValidate, + oLookup, + oLoadCRL, + oSquidMode, + oPEM, + oEscapedPEM, + oForceDefaultResponder + }; + + +/* The list of options as used by the argparse.c code. */ +static ARGPARSE_OPTS opts[] = { + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") }, + { oPing, "ping", 0, N_("check whether a dirmngr is running")}, + { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")}, + { oValidate, "validate", 0, N_("validate a certificate")}, + { oLookup, "lookup", 0, N_("lookup a certificate")}, + { oLocal, "local", 0, N_("lookup only locally stored certificates")}, + { oUrl, "url", 0, N_("expect an URL for --lookup")}, + { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")}, + { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")}, + { oPEM, "pem", 0, N_("certificates are expected in PEM format")}, + { oForceDefaultResponder, "force-default-responder", 0, + N_("force the use of the default OCSP responder")}, + { 0, NULL, 0, NULL } +}; + + +/* The usual structure for the program flags. */ +static struct +{ + int quiet; + int verbose; + const char *dirmngr_program; + int force_pipe_server; + int force_default_responder; + int pem; + int escaped_pem; /* PEM is additional percent encoded. */ + int url; /* Expect an URL. */ + int local; /* Lookup up only local certificates. */ + + int use_ocsp; +} opt; + + +/* Communication structure for the certificate inquire callback. */ +struct inq_cert_parm_s +{ + assuan_context_t ctx; + const unsigned char *cert; + size_t certlen; +}; + + +/* Base64 conversion tables. */ +static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +static unsigned char asctobin[256]; /* runtime initialized */ + + +/* Prototypes. */ +static assuan_context_t start_dirmngr (int only_daemon); +static gpg_error_t read_certificate (const char *fname, + unsigned char **rbuf, size_t *rbuflen); +static gpg_error_t do_check (assuan_context_t ctx, + const unsigned char *cert, size_t certlen); +static gpg_error_t do_cache (assuan_context_t ctx, + const unsigned char *cert, size_t certlen); +static gpg_error_t do_validate (assuan_context_t ctx, + const unsigned char *cert, size_t certlen); +static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename); +static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern); +static gpg_error_t squid_loop_body (assuan_context_t ctx); + + + +/* Function called by argparse.c to display information. */ +static const char * +my_strusage (int level) +{ + const char *p; + + switch(level) + { + case 11: p = "dirmngr-client (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 49: p = PACKAGE_BUGREPORT; break; + case 1: + case 40: p = + _("Usage: dirmngr-client [options] " + "[certfile|pattern] (-h for help)\n"); + break; + case 41: p = + _("Syntax: dirmngr-client [options] [certfile|pattern]\n" + "Test an X.509 certificate against a CRL or do an OCSP check\n" + "The process returns 0 if the certificate is valid, 1 if it is\n" + "not valid and other error codes for general failures\n"); + break; + + default: p = NULL; + } + return p; +} + + +static void +my_i18n_init (void) +{ +#warning Better use common init functions +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file (PACKAGE); +#else +# ifdef ENABLE_NLS + setlocale (LC_ALL, "" ); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +# endif +#endif +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + assuan_context_t ctx; + gpg_error_t err; + unsigned char *certbuf; + size_t certbuflen = 0; + int cmd_ping = 0; + int cmd_cache_cert = 0; + int cmd_validate = 0; + int cmd_lookup = 0; + int cmd_loadcrl = 0; + int cmd_squid_mode = 0; + + set_strusage (my_strusage); + log_set_prefix ("dirmngr-client", + JNLIB_LOG_WITH_PREFIX); + + /* For W32 we need to initialize the socket subsystem. Becuase we + don't use Pth we need to do this explicit. */ +#ifdef HAVE_W32_SYSTEM + { + WSADATA wsadat; + + WSAStartup (0x202, &wsadat); + } +#endif /*HAVE_W32_SYSTEM*/ + + /* Init Assuan. */ + assuan_set_assuan_log_prefix (log_get_prefix (NULL)); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + + /* Setup I18N. */ + my_i18n_init(); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* Do not remove the args. */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oQuiet: opt.quiet++; break; + + case oOCSP: opt.use_ocsp++; break; + case oPing: cmd_ping = 1; break; + case oCacheCert: cmd_cache_cert = 1; break; + case oValidate: cmd_validate = 1; break; + case oLookup: cmd_lookup = 1; break; + case oUrl: opt.url = 1; break; + case oLocal: opt.local = 1; break; + case oLoadCRL: cmd_loadcrl = 1; break; + case oPEM: opt.pem = 1; break; + case oSquidMode: + opt.pem = 1; + opt.escaped_pem = 1; + cmd_squid_mode = 1; + break; + case oForceDefaultResponder: opt.force_default_responder = 1; break; + + default : pargs.err = 2; break; + } + } + if (log_get_errorcount (0)) + exit (2); + + /* Build the helptable for radix64 to bin conversion. */ + if (opt.pem) + { + int i; + unsigned char *s; + + for (i=0; i < 256; i++ ) + asctobin[i] = 255; /* Used to detect invalid characters. */ + for (s=bintoasc, i=0; *s; s++, i++) + asctobin[*s] = i; + } + + + if (cmd_ping) + err = 0; + else if (cmd_lookup || cmd_loadcrl) + { + if (!argc) + usage (1); + err = 0; + } + else if (cmd_squid_mode) + { + err = 0; + if (argc) + usage (1); + } + else if (!argc) + { + err = read_certificate (NULL, &certbuf, &certbuflen); + if (err) + log_error (_("error reading certificate from stdin: %s\n"), + gpg_strerror (err)); + } + else if (argc == 1) + { + err = read_certificate (*argv, &certbuf, &certbuflen); + if (err) + log_error (_("error reading certificate from `%s': %s\n"), + *argv, gpg_strerror (err)); + } + else + { + err = 0; + usage (1); + } + + if (log_get_errorcount (0)) + exit (2); + + if (certbuflen > 20000) + { + log_error (_("certificate too large to make any sense\n")); + exit (2); + } + + ctx = start_dirmngr (1); + if (!ctx) + exit (2); + + if (cmd_ping) + ; + else if (cmd_squid_mode) + { + while (!(err = squid_loop_body (ctx))) + ; + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + } + else if (cmd_lookup) + { + int last_err = 0; + + for (; argc; argc--, argv++) + { + err = do_lookup (ctx, *argv); + if (err) + { + log_error (_("lookup failed: %s\n"), gpg_strerror (err)); + last_err = err; + } + } + err = last_err; + } + else if (cmd_loadcrl) + { + int last_err = 0; + + for (; argc; argc--, argv++) + { + err = do_loadcrl (ctx, *argv); + if (err) + { + log_error (_("loading CRL `%s' failed: %s\n"), + *argv, gpg_strerror (err)); + last_err = err; + } + } + err = last_err; + } + else if (cmd_cache_cert) + { + err = do_cache (ctx, certbuf, certbuflen); + xfree (certbuf); + } + else if (cmd_validate) + { + err = do_validate (ctx, certbuf, certbuflen); + xfree (certbuf); + } + else + { + err = do_check (ctx, certbuf, certbuflen); + xfree (certbuf); + } + + assuan_release (ctx); + + if (cmd_ping) + { + if (!opt.quiet) + log_info (_("a dirmngr daemon is up and running\n")); + return 0; + } + else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode) + return err? 1:0; + else if (cmd_cache_cert) + { + if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE ) + { + if (!opt.quiet) + log_info (_("certificate already cached\n")); + } + else if (err) + { + log_error (_("error caching certificate: %s\n"), + gpg_strerror (err)); + return 1; + } + return 0; + } + else if (cmd_validate && err) + { + log_error (_("validation of certificate failed: %s\n"), + gpg_strerror (err)); + return 1; + } + else if (!err) + { + if (!opt.quiet) + log_info (_("certificate is valid\n")); + return 0; + } + else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) + { + if (!opt.quiet) + log_info (_("certificate has been revoked\n")); + return 1; + } + else + { + log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); + return 2; + } +} + + +/* Print status line from the assuan protocol. */ +static gpg_error_t +status_cb (void *opaque, const char *line) +{ + (void)opaque; + + if (opt.verbose > 2) + log_info (_("got status: `%s'\n"), line); + return 0; +} + +/* Print data as retrieved by the lookup function. */ +static gpg_error_t +data_cb (void *opaque, const void *buffer, size_t length) +{ + gpg_error_t err; + struct b64state *state = opaque; + + if (buffer) + { + err = b64enc_write (state, buffer, length); + if (err) + log_error (_("error writing base64 encoding: %s\n"), + gpg_strerror (err)); + } + return 0; +} + + +/* Try to connect to the dirmngr via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static assuan_context_t +start_dirmngr (int only_daemon) +{ + int rc; + char *infostr, *p; + assuan_context_t ctx; + int try_default = 0; + + infostr = opt.force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); + if (only_daemon && (!infostr || !*infostr)) + { + infostr = xstrdup (dirmngr_socket_name ()); + try_default = 1; + } + + rc = assuan_new (&ctx); + if (rc) + { + log_error (_("failed to allocate assuan context: %s\n"), + gpg_strerror (rc)); + return NULL; + } + + if (!infostr || !*infostr) + { + const char *pgmname; + const char *argv[3]; + int no_close_list[3]; + int i; + + if (only_daemon) + { + log_error (_("apparently no running dirmngr\n")); + return NULL; + } + + if (opt.verbose) + log_info (_("no running dirmngr - starting one\n")); + + if (!opt.dirmngr_program || !*opt.dirmngr_program) + opt.dirmngr_program = "./dirmngr"; + if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) + pgmname = opt.dirmngr_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + if (log_get_fd () != -1) + no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); + no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); + no_close_list[i] = -1; + + /* Connect to the agent and perform initial handshaking. */ + rc = assuan_pipe_connect (ctx, opt.dirmngr_program, argv, + no_close_list, NULL, NULL, 0); + } + else /* Connect to a daemon. */ + { + int prot; + int pid; + + infostr = xstrdup (infostr); + if (!try_default && *infostr) + { + if ( !(p = strchr (infostr, ':')) || p == infostr) + { + log_error (_("malformed DIRMNGR_INFO environment variable\n")); + xfree (infostr); + if (only_daemon) + return NULL; + /* Try again by starting a new instance. */ + opt.force_pipe_server = 1; + return start_dirmngr (0); + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + log_error (_("dirmngr protocol version %d is not supported\n"), + prot); + xfree (infostr); + if (only_daemon) + return NULL; + opt.force_pipe_server = 1; + return start_dirmngr (0); + } + } + else + pid = -1; + + rc = assuan_socket_connect (ctx, infostr, pid, 0); + xfree (infostr); + if (gpg_err_code(rc) == GPG_ERR_ASS_CONNECT_FAILED && !only_daemon) + { + log_error (_("can't connect to the dirmngr - trying fall back\n")); + opt.force_pipe_server = 1; + return start_dirmngr (0); + } + } + + if (rc) + { + assuan_release (ctx); + log_error (_("can't connect to the dirmngr: %s\n"), + gpg_strerror (rc)); + return NULL; + } + + return ctx; +} + + +/* Read the first PEM certificate from the file FNAME. If fname is + NULL the next certificate is read from stdin. The certificate is + returned in an alloced buffer whose address will be returned in + RBUF and its length in RBUFLEN. */ +static gpg_error_t +read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) +{ + FILE *fp; + int c; + int pos; + int value; + unsigned char *buf; + size_t bufsize, buflen; + enum { + s_init, s_idle, s_lfseen, s_begin, + s_b64_0, s_b64_1, s_b64_2, s_b64_3, + s_waitend + } state = s_init; + + fp = fname? fopen (fname, "r") : stdin; + if (!fp) + return gpg_error_from_errno (errno); + + pos = 0; + value = 0; + bufsize = 8192; + buf = xmalloc (bufsize); + buflen = 0; + while ((c=getc (fp)) != EOF) + { + int escaped_c = 0; + + if (opt.escaped_pem) + { + if (c == '%') + { + char tmp[2]; + if ((c = getc(fp)) == EOF) + break; + tmp[0] = c; + if ((c = getc(fp)) == EOF) + break; + tmp[1] = c; + if (!hexdigitp (tmp) || !hexdigitp (tmp+1)) + { + log_error ("invalid percent escape sequence\n"); + state = s_idle; /* Force an error. */ + /* Skip to end of line. */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + goto ready; + } + c = xtoi_2 (tmp); + escaped_c = 1; + } + else if (c == '\n') + goto ready; /* Ready. */ + } + switch (state) + { + case s_idle: + if (c == '\n') + { + state = s_lfseen; + pos = 0; + } + break; + case s_init: + state = s_lfseen; + case s_lfseen: + if (c != "-----BEGIN "[pos]) + state = s_idle; + else if (pos == 10) + state = s_begin; + else + pos++; + break; + case s_begin: + if (c == '\n') + state = s_b64_0; + break; + case s_b64_0: + case s_b64_1: + case s_b64_2: + case s_b64_3: + { + if (buflen >= bufsize) + { + bufsize += 8192; + buf = xrealloc (buf, bufsize); + } + + if (c == '-') + state = s_waitend; + else if ((c = asctobin[c & 0xff]) == 255 ) + ; /* Just skip invalid base64 characters. */ + else if (state == s_b64_0) + { + value = c << 2; + state = s_b64_1; + } + else if (state == s_b64_1) + { + value |= (c>>4)&3; + buf[buflen++] = value; + value = (c<<4)&0xf0; + state = s_b64_2; + } + else if (state == s_b64_2) + { + value |= (c>>2)&15; + buf[buflen++] = value; + value = (c<<6)&0xc0; + state = s_b64_3; + } + else + { + value |= c&0x3f; + buf[buflen++] = value; + state = s_b64_0; + } + } + break; + case s_waitend: + /* Note that we do not check that the base64 decoder has + been left in the expected state. We assume that the PEM + header is just fine. However we need to wait for the + real LF and not a trailing percent escaped one. */ + if (c== '\n' && !escaped_c) + goto ready; + break; + default: + BUG(); + } + } + ready: + if (fname) + fclose (fp); + + if (state == s_init && c == EOF) + { + xfree (buf); + return gpg_error (GPG_ERR_EOF); + } + else if (state != s_waitend) + { + log_error ("no certificate or invalid encoded\n"); + xfree (buf); + return gpg_error (GPG_ERR_INV_ARMOR); + } + + *rbuf = buf; + *rbuflen = buflen; + return 0; +} + +/* Read a binary certificate from the file FNAME. If fname is NULL the + file is read from stdin. The certificate is returned in an alloced + buffer whose address will be returned in RBUF and its length in + RBUFLEN. */ +static gpg_error_t +read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) +{ + gpg_error_t err; + FILE *fp; + unsigned char *buf; + size_t nread, bufsize, buflen; + + if (opt.pem) + return read_pem_certificate (fname, rbuf, rbuflen); + + fp = fname? fopen (fname, "rb") : stdin; + if (!fp) + return gpg_error_from_errno (errno); + + buf = NULL; + bufsize = buflen = 0; +#define NCHUNK 8192 + do + { + bufsize += NCHUNK; + if (!buf) + buf = xmalloc (bufsize); + else + buf = xrealloc (buf, bufsize); + + nread = fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && ferror (fp)) + { + err = gpg_error_from_errno (errno); + xfree (buf); + if (fname) + fclose (fp); + return err; + } + buflen += nread; + } + while (nread == NCHUNK); +#undef NCHUNK + if (fname) + fclose (fp); + *rbuf = buf; + *rbuflen = buflen; + return 0; +} + + +/* Callback for the inquire fiunction to send back the certificate. */ +static gpg_error_t +inq_cert (void *opaque, const char *line) +{ + struct inq_cert_parm_s *parm = opaque; + gpg_error_t err; + + if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10])) + { + err = assuan_send_data (parm->ctx, parm->cert, parm->certlen); + } + else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) + { + /* We don't support this but dirmngr might ask for it. So + simply ignore it by sending back and empty value. */ + err = assuan_send_data (parm->ctx, NULL, 0); + } + else if (!strncmp (line, "SENDCERT_SKI", 12) + && (line[12]==' ' || !line[12])) + { + /* We don't support this but dirmngr might ask for it. So + simply ignore it by sending back an empty value. */ + err = assuan_send_data (parm->ctx, NULL, 0); + } + else if (!strncmp (line, "SENDISSUERCERT", 14) + && (line[14] == ' ' || !line[14])) + { + /* We don't support this but dirmngr might ask for it. So + simply ignore it by sending back an empty value. */ + err = assuan_send_data (parm->ctx, NULL, 0); + } + else + { + log_info (_("unsupported inquiry `%s'\n"), line); + err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + /* Note that this error will let assuan_transact terminate + immediately instead of return the error to the caller. It is + not clear whether this is the desired behaviour - it may + change in future. */ + } + + return err; +} + + +/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. + Return a proper error code. */ +static gpg_error_t +do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + struct inq_cert_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.ctx = ctx; + parm.cert = cert; + parm.certlen = certlen; + + err = assuan_transact (ctx, + (opt.use_ocsp && opt.force_default_responder + ? "CHECKOCSP --force-default-responder" + : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"), + NULL, NULL, inq_cert, &parm, status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + return err; +} + +/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. + Return a proper error code. */ +static gpg_error_t +do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + struct inq_cert_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.ctx = ctx; + parm.cert = cert; + parm.certlen = certlen; + + err = assuan_transact (ctx, "CACHECERT", NULL, NULL, + inq_cert, &parm, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + return err; +} + +/* Check the certificate CERT,CERTLEN for validity using dirmngrs + internal validate feature. Return a proper error code. */ +static gpg_error_t +do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + struct inq_cert_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.ctx = ctx; + parm.cert = cert; + parm.certlen = certlen; + + err = assuan_transact (ctx, "VALIDATE", NULL, NULL, + inq_cert, &parm, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + return err; +} + +/* Load a CRL into the dirmngr. */ +static gpg_error_t +do_loadcrl (assuan_context_t ctx, const char *filename) +{ + gpg_error_t err; + const char *s; + char *fname, *line, *p; + + if (opt.url) + fname = xstrdup (filename); + else + { +#ifdef HAVE_CANONICALIZE_FILE_NAME + fname = canonicalize_file_name (filename); + if (!fname) + { + log_error ("error canonicalizing `%s': %s\n", + filename, strerror (errno)); + return gpg_error (GPG_ERR_GENERAL); + } +#else + fname = xstrdup (filename); +#endif + if (*fname != '/') + { + log_error (_("absolute file name expected\n")); + return gpg_error (GPG_ERR_GENERAL); + } + } + + line = xmalloc (8 + 6 + strlen (fname) * 3 + 1); + p = stpcpy (line, "LOADCRL "); + if (opt.url) + p = stpcpy (p, "--url "); + for (s = fname; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + + err = assuan_transact (ctx, line, NULL, NULL, + NULL, NULL, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + xfree (line); + xfree (fname); + return err; +} + + +/* Do a LDAP lookup using PATTERN and print the result in a base-64 + encoded format. */ +static gpg_error_t +do_lookup (assuan_context_t ctx, const char *pattern) +{ + gpg_error_t err; + const unsigned char *s; + char *line, *p; + struct b64state state; + + if (opt.verbose) + log_info (_("looking up `%s'\n"), pattern); + + err = b64enc_start (&state, stdout, NULL); + if (err) + return err; + + line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); + + p = stpcpy (line, "LOOKUP "); + if (opt.url) + p = stpcpy (p, "--url "); + if (opt.local) + p = stpcpy (p, "--cache-only "); + for (s=pattern; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + + + err = assuan_transact (ctx, line, + data_cb, &state, + NULL, NULL, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + + err = b64enc_finish (&state); + + xfree (line); + return err; +} + +/* The body of an endless loop: Read a line from stdin, retrieve the + certificate from it, validate it and print "ERR" or "OK" to stdout. + Continue. */ +static gpg_error_t +squid_loop_body (assuan_context_t ctx) +{ + gpg_error_t err; + unsigned char *certbuf; + size_t certbuflen = 0; + + err = read_pem_certificate (NULL, &certbuf, &certbuflen); + if (gpg_err_code (err) == GPG_ERR_EOF) + return err; + if (err) + { + log_error (_("error reading certificate from stdin: %s\n"), + gpg_strerror (err)); + puts ("ERROR"); + return 0; + } + + err = do_check (ctx, certbuf, certbuflen); + xfree (certbuf); + if (!err) + { + if (opt.verbose) + log_info (_("certificate is valid\n")); + puts ("OK"); + } + else + { + if (!opt.quiet) + { + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) + log_info (_("certificate has been revoked\n")); + else + log_error (_("certificate check failed: %s\n"), + gpg_strerror (err)); + } + puts ("ERROR"); + } + + fflush (stdout); + + return 0; +} diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c new file mode 100644 index 000000000..12b74bd00 --- /dev/null +++ b/dirmngr/dirmngr.c @@ -0,0 +1,1829 @@ +/* dirmngr.c - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_W32_SYSTEM +#include +#include +#endif +#include +#include +#include +#include + + +#define JNLIB_NEED_LOG_LOGV +#include "dirmngr.h" + +#include + +#include "certcache.h" +#include "crlcache.h" +#include "crlfetch.h" +#include "misc.h" +#include "ldapserver.h" +#include "asshelp.h" + +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, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oLogFile, + oBatch, + oDisableHTTP, + oDisableLDAP, + oIgnoreLDAPDP, + oIgnoreHTTPDP, + oIgnoreOCSPSvcUrl, + oHonorHTTPProxy, + oHTTPProxy, + oLDAPProxy, + oOnlyLDAPProxy, + oLDAPFile, + oLDAPTimeout, + oLDAPAddServers, + oOCSPResponder, + oOCSPSigner, + oOCSPMaxClockSkew, + oOCSPMaxPeriod, + oOCSPCurrentPeriod, + oMaxReplies, + oFakedSystemTime, + oForce, + oAllowOCSP, + oSocketName, + oLDAPWrapperProgram, + oHTTPWrapperProgram, + oIgnoreCertExtension, + 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 HAVE_W32_SYSTEM + 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 (oSocketName, "socket-name", N_("|FILE|listen on socket FILE")), + + ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ + ARGPARSE_p_u (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + 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 () +}; + +#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; + +/* 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 we will 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; + +/* 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. */ +#ifdef HAVE_W32_SYSTEM +#define TIMERTICK_INTERVAL (4) +#else +#define TIMERTICK_INTERVAL (2) /* Seconds. */ +#endif + +/* 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 (pth_key_getdata 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); +static ldap_server_t parse_ldapserver_file (const char* filename); +static fingerprint_list_t parse_ocsp_signer (const char *string); +static void handle_connections (assuan_fd_t listen_fd); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_PTH_IMPL; + +GCRY_THREAD_OPTION_PTH_IMPL; +static int fixed_gcry_pth_init (void) +{ + return pth_self ()? 0 : (pth_init () == FALSE) ? errno : 0; +} + +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" + "LDAP 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; +} + + +/* 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_ASSUAN_VALUE; + else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) + opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE); + else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_ASSUAN_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); +} + + +static void +wrong_args (const char *text) +{ + fputs (_("usage: dirmngr [options] "), stderr); + fputs (text, stderr); + putc ('\n', stderr); + dirmngr_exit (2); +} + + +/* Helper to start the reaper thread for the ldap wrapper. */ +static void +launch_reaper_thread (void) +{ + static int done; + pth_attr_t tattr; + + if (done) + return; + done = 1; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper"); + + if (!pth_spawn (tattr, ldap_wrapper_thread, NULL)) + { + log_error (_("error spawning ldap wrapper reaper thread: %s\n"), + strerror (errno) ); + dirmngr_exit (1); + } + pth_attr_destroy (tattr); +} + + +/* Helper to stop the reaper thread for the ldap wrapper. */ +static void +shutdown_reaper (void) +{ + ldap_wrapper_wait_connections (); +} + + +/* 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); + return 1; + } + + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: opt.debug |= pargs->r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs->r.ret_str; 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 oIgnoreCertExtension: + add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str); + break; + + default: + return 0; /* Not handled. */ + } + + return 1; /* Handled. */ +} + + +#ifdef HAVE_W32_SYSTEM +/* 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) +{ + /* 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 /*HAVE_W32_SYSTEM*/ + +#ifndef HAVE_W32_SYSTEM +static int +pid_suffix_callback (unsigned long *r_suffix) +{ + union int_and_ptr_u value; + + value.aptr = pth_key_getdata (my_tlskey_current_fd); + *r_suffix = value.aint; + return (*r_suffix != -1); /* Use decimal representation. */ +} +#endif /*!HAVE_W32_SYSTEM*/ + + +#ifdef HAVE_W32_SYSTEM +#define main real_main +#endif +int +main (int argc, char **argv) +{ +#ifdef HAVE_W32_SYSTEM +#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; + char *ldapfile = NULL; + int debug_wait = 0; + int rc; + int homedir_seen = 0; + struct assuan_malloc_hooks malloc_hooks; + +#ifdef HAVE_W32_SYSTEM + /* 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 /*HAVE_W32_SYSTEM*/ + + set_strusage (my_strusage); + log_set_prefix ("dirmngr", 1|4); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + /* Libgcrypt requires us to register the threading model first. + Note that this will also do the pth_init. */ + gcry_threads_pth.init = fixed_gcry_pth_init; + rc = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth); + if (rc) + { + log_fatal ("can't register GNU Pth with Libgcrypt: %s\n", + gpg_strerror (rc)); + } + 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 (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt", + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + 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 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_PTH); + 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; + + opt.homedir = default_homedir (); + + /* Now with Pth running we can set the logging callback. Our + windows implementation does not yet feature the Pth TLS + functions. */ +#ifndef HAVE_W32_SYSTEM + if (pth_key_create (&my_tlskey_current_fd, NULL)) + if (pth_key_setdata (my_tlskey_current_fd, NULL)) + 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. */ + socket_name = dirmngr_socket_name (); + + /* 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) + { + opt.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/dirmngr as default home directory. Note, that + this also overrides the GNUPGHOME environment variable. */ + if (opt.system_daemon && !homedir_seen) + { + opt.homedir = gnupg_sysconfdir (); + opt.homedir_data = gnupg_datadir (); + opt.homedir_cache = gnupg_cachedir (); + } + + if (default_config) + configname = make_filename (opt.homedir, "dirmngr.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 oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs.r.ret_str; 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: ldapfile = pargs.r.ret_str; 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_data) + opt.homedir_data = opt.homedir; + if (!opt.homedir_cache) + opt.homedir_cache = opt.homedir; + + if (greeting) + { + fprintf (stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf (stderr, "%s\n", strusage(15) ); + } + +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + if (gnupg_faked_time_p ()) + { + gnupg_isotime_t tbuf; + gnupg_get_isotime (tbuf); + log_info (_("WARNING: running with faked system time %s\n"), tbuf); + } + + set_debug (); + + /* Get LDAP server list from file. */ + if (!ldapfile) + { + ldapfile = make_filename (opt.homedir, + opt.system_daemon? + "ldapservers.conf":"dirmngr_ldapservers.conf", + NULL); + opt.ldapservers = parse_ldapserver_file (ldapfile); + xfree (ldapfile); + } + else + opt.ldapservers = parse_ldapserver_file (ldapfile); + +#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) + cmd = aServer; + rc = 0; + + if (cmd == aServer) + { + if (argc) + wrong_args ("--server"); + + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, 2|4); + } + + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + launch_reaper_thread (); + 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, (JNLIB_LOG_WITH_PREFIX + |JNLIB_LOG_WITH_TIME + |JNLIB_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 + if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) + { + log_error (_("name of socket too long\n")); + dirmngr_exit (1); + } + + 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); + } + + memset (&serv_addr, 0, sizeof serv_addr); + serv_addr.sun_family = AF_UNIX; + strcpy (serv_addr.sun_path, socket_name); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen (serv_addr.sun_path) + 1); + + rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len); + if (rc == -1 && errno == EADDRINUSE) + { + remove (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 (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"), socket_name ); + + fflush (NULL); + +#ifdef HAVE_W32_SYSTEM + pid = getpid (); + printf ("set DIRMNGR_INFO=%s;%lu;1\n", socket_name, (ulong) pid); +#else + pid = pth_fork (); + if (pid == (pid_t)-1) + { + log_fatal (_("fork failed: %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, "DIRMNGR_INFO=%s:%lu:1", + socket_name, (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 */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + printf ( "setenv %s\n", infostr); + } + else + { + printf ( "%s; export DIRMNGR_INFO;\n", infostr); + } + 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 | JNLIB_LOG_RUN_DETACHED); + opt.running_detached = 1; + + if (chdir("/")) + { + log_error (_("chdir to / failed: %s\n"), strerror (errno)); + dirmngr_exit (1); + } + } +#endif + + launch_reaper_thread (); + cert_cache_init (); + crl_cache_init (); +#ifdef HAVE_W32_SYSTEM + 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 HAVE_W32_SYSTEM + 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"); + launch_reaper_thread (); + crl_cache_init (); + crl_cache_list (stdout); + } + else if (cmd == aLoadCRL) + { + struct server_control_s ctrlbuf; + + memset (&ctrlbuf, 0, sizeof ctrlbuf); + dirmngr_init_default_ctrl (&ctrlbuf); + + launch_reaper_thread (); + 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); + } + } + 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); + + launch_reaper_thread (); + 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); + } + } + 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; + + /* List options and default values in the GPG Conf format. */ + +/* The following list is taken from gnupg/tools/gpgconf-comp.c. */ +/* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING + FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ +#define GC_OPT_FLAG_NONE 0UL +/* The DEFAULT flag for an option indicates that the option has a + default value. */ +#define GC_OPT_FLAG_DEFAULT (1UL << 4) +/* The DEF_DESC flag for an option indicates that the option has a + default, which is described by the value of the default field. */ +#define GC_OPT_FLAG_DEF_DESC (1UL << 5) +/* The NO_ARG_DESC flag for an option indicates that the argument has + a default, which is described by the value of the ARGDEF field. */ +#define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) +#define GC_OPT_FLAG_NO_CHANGE (1UL <<7) + +#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 (opt.homedir, + "dirmngr.conf", NULL ); + + filename = percent_escape (opt.config_filename, NULL); + printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", + GC_OPT_FLAG_DEFAULT, filename); + xfree (filename); + + printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); + printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); + 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 (opt.homedir, + opt.system_daemon? + "ldapservers.conf":"dirmngr_ldapservers.conf", + NULL); + filename_esc = percent_escape (filename, NULL); + printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, + filename_esc); + xfree (filename_esc); + xfree (filename); + + printf ("ldaptimeout:%lu:%u\n", + flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT); + printf ("max-replies:%lu:%u\n", + flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES); + printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); + + printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); + + printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); + printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); + printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); + 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. */ + printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); + } + cleanup (); + return !!rc; +} + + +#ifdef HAVE_W32_SYSTEM +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 [] = + { + /* Ignore warning. */ + { "DirMngr", &real_main }, + { NULL, NULL } + }; + + if (!StartServiceCtrlDispatcher (DispatchTable)) + return 1; + return 0; + } +} +#endif + + +static void +cleanup (void) +{ + crl_cache_deinit (); + cert_cache_deinit (1); + + ldapserver_list_free (opt.ldapservers); + opt.ldapservers = NULL; + + if (cleanup_socket) + { + cleanup_socket = 0; + if (socket_name && *socket_name) + remove (socket_name); + } +} + + +void +dirmngr_exit (int rc) +{ + cleanup (); + exit (rc); +} + + +void +dirmngr_init_default_ctrl (ctrl_t ctrl) +{ + (void)ctrl; + + /* Nothing for now. */ +} + + +/* 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 + +*/ +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; + FILE *fp; + + fp = fopen (filename, "r"); + if (!fp) + { + log_error (_("error opening `%s': %s\n"), filename, strerror (errno)); + return NULL; + } + + serverstart = NULL; + serverend = &serverstart; + while (fgets (buffer, sizeof buffer, fp)) + { + lineno++; + if (!*buffer || buffer[strlen(buffer)-1] != '\n') + { + if (*buffer && feof (fp)) + ; /* Last line not terminated - continue. */ + else + { + log_error (_("%s:%u: line too long - skipped\n"), + filename, lineno); + while ( (c=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 (ferror (fp)) + log_error (_("error reading `%s': %s\n"), filename, strerror (errno)); + fclose (fp); + + return serverstart; +} + + +static fingerprint_list_t +parse_ocsp_signer (const char *string) +{ + gpg_error_t err; + char *fname; + FILE *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 (opt.homedir, string, NULL); + } + + fp = 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 (!fgets (line, DIM(line)-1, fp) ) + { + if (!feof (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("%s:%u: read error: %s\n"), + fname, lnr, gpg_strerror (err)); + errflag = 1; + } + 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=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 PTH + 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 (); +} + + + +/* The signal handler. */ +static void +handle_signal (int signo) +{ + switch (signo) + { +#ifndef HAVE_W32_SYSTEM + case SIGHUP: + 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 (); + 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; +#endif + default: + log_info (_("signal %d received - no action defined\n"), signo); + } +} + + +/* This is the worker for the ticker. It is called every few seconds + and may only do fast operations. */ +static void +handle_tick (void) +{ + /* Nothing real to do right now. Actually we need the timeout only + for W32 where 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*/ +} + + +/* Check the nonce on a new connection. This is a NOP unless we 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 fucntion. */ +static void * +start_connection_thread (void *arg) +{ + union int_and_ptr_u argval; + assuan_fd_t fd; + + argval.aptr = arg; + fd = argval.afd; + + if (check_nonce (fd, &socket_nonce)) + return NULL; + +#ifndef HAVE_W32_SYSTEM + pth_key_setdata (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; + pth_key_setdata (my_tlskey_current_fd, argval.aptr); +#endif + + return NULL; +} + + +/* Main loop in daemon mode. */ +static void +handle_connections (assuan_fd_t listen_fd) +{ + pth_attr_t tattr; + pth_event_t ev, time_ev; + sigset_t sigs, oldsigs; + int signo; + struct sockaddr_un paddr; + socklen_t plen = sizeof( paddr ); + assuan_fd_t fd; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 1024*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "dirmngr"); + +#ifndef HAVE_W32_SYSTEM /* FIXME */ + sigemptyset (&sigs ); + sigaddset (&sigs, SIGHUP); + sigaddset (&sigs, SIGUSR1); + sigaddset (&sigs, SIGUSR2); + sigaddset (&sigs, SIGINT); + sigaddset (&sigs, SIGTERM); + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); +#else + sigs = 0; + ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); +#endif + time_ev = NULL; + + for (;;) + { + if (shutdown_pending) + { + if (!active_connections) + break; /* ready */ + + /* Do not accept anymore connections but wait for existing + connections to terminate. */ + signo = 0; + pth_wait (ev); + if (pth_event_occurred (ev) && signo) + handle_signal (signo); + continue; + } + + if (!time_ev) + time_ev = pth_event (PTH_EVENT_TIME, + pth_timeout (TIMERTICK_INTERVAL, 0)); + + if (time_ev) + pth_event_concat (ev, time_ev, NULL); + fd = (assuan_fd_t) pth_accept_ev (FD2INT (listen_fd), (struct sockaddr *)&paddr, &plen, ev); + if (time_ev) + pth_event_isolate (time_ev); + + if (fd == ASSUAN_INVALID_FD) + { + if (pth_event_occurred (ev) + || (time_ev && pth_event_occurred (time_ev)) ) + { + if (pth_event_occurred (ev)) + handle_signal (signo); + if (time_ev && pth_event_occurred (time_ev)) + { + pth_event_free (time_ev, PTH_FREE_ALL); + time_ev = NULL; + handle_tick (); + } + continue; + } + log_error (_("accept failed: %s - waiting 1s\n"), strerror (errno)); + pth_sleep (1); + continue; + } + + if (pth_event_occurred (ev)) + { + handle_signal (signo); + } + + if (time_ev && pth_event_occurred (time_ev)) + { + pth_event_free (time_ev, PTH_FREE_ALL); + time_ev = NULL; + handle_tick (); + } + + + /* We now might create a new thread and because we don't want + any signals (as we are handling them here) to be delivered to + a new thread we need to block those signals. */ + pth_sigmask (SIG_BLOCK, &sigs, &oldsigs); + + /* Create thread to handle this connection. */ + { + union int_and_ptr_u argval; + + argval.afd = fd; + if (!pth_spawn (tattr, start_connection_thread, argval.aptr)) + { + log_error (_("error spawning connection handler: %s\n"), + strerror (errno) ); + assuan_sock_close (fd); + } + } + + /* Restore the signal mask. */ + pth_sigmask (SIG_SETMASK, &oldsigs, NULL); + } + + pth_event_free (ev, PTH_FREE_ALL); + if (time_ev) + pth_event_free (time_ev, PTH_FREE_ALL); + pth_attr_destroy (tattr); + cleanup (); + log_info ("%s %s stopped\n", strusage(11), strusage(13)); +} diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h new file mode 100644 index 000000000..e6fa0d318 --- /dev/null +++ b/dirmngr/dirmngr.h @@ -0,0 +1,189 @@ +/* dirmngr.h - Common definitions for the dirmngr + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2004 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 . + */ + +#ifndef DIRMNGR_H +#define DIRMNGR_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_DIRMNGR +#include +#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" + + +/* 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; + + +/* 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; /* Configuration directory name */ + const char *homedir_data; /* Ditto for data files (/usr/share/dirmngr). */ + const char *homedir_cache; /* Ditto 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 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; /* Use given 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. */ +} opt; + + +#define DBG_X509_VALUE 1 /* debug x.509 parsing */ +#define DBG_LOOKUP_VALUE 2 /* debug lookup details */ +#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_ASSUAN_VALUE 1024 /* debug assuan communication */ + +#define DBG_X509 (opt.debug & DBG_X509_VALUE) +#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_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_ASSUAN (opt.debug & DBG_ASSUAN_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. */ +}; + + +/*-- dirmngr.c --*/ +void dirmngr_exit( int ); /* Wrapper for exit() */ +void dirmngr_init_default_ctrl (ctrl_t ctrl); + +/*-- 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_tick (ctrl_t ctrl); + + +#endif /*DIRMNGR_H*/ diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c new file mode 100644 index 000000000..b73cc7da6 --- /dev/null +++ b/dirmngr/dirmngr_ldap.c @@ -0,0 +1,646 @@ +/* dirmngr-ldap.c - The LDAP helper for dirmngr. + * Copyright (C) 2004 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 +#include +#include +#include +#include + +#ifdef HAVE_W32_SYSTEM +#include +#include +#include +#include "ldap-url.h" +#else +/* For OpenLDAP, to enable the API that we're using. */ +#define LDAP_DEPRECATED 1 +#include +#endif + + +#define JNLIB_NEED_LOG_LOGV +#include "../common/logging.h" +#include "../common/argparse.h" +#include "../common/stringhelp.h" +#include "../common/mischelp.h" +#include "../common/strlist.h" + +#include "i18n.h" +#include "util.h" + +#define DEFAULT_LDAP_TIMEOUT 100 /* Arbitrary long timeout. */ + + +/* Constants for the options. */ +enum + { + oQuiet = 'q', + oVerbose = 'v', + + oTimeout = 500, + oMulti, + oProxy, + oHost, + oPort, + oUser, + oPass, + oEnvPass, + oDN, + oFilter, + oAttr, + + oOnlySearchTimeout, + oLogWithPID + }; + + +/* The list of options as used by the argparse.c code. */ +static ARGPARSE_OPTS opts[] = { + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oTimeout, "timeout", 1, N_("|N|set LDAP timeout to N seconds")}, + { oMulti, "multi", 0, N_("return all values in" + " a record oriented format")}, + { oProxy, "proxy", 2, + N_("|NAME|ignore host part and connect through NAME")}, + { oHost, "host", 2, N_("|NAME|connect to host NAME")}, + { oPort, "port", 1, N_("|N|connect to port N")}, + { oUser, "user", 2, N_("|NAME|use user NAME for authentication")}, + { oPass, "pass", 2, N_("|PASS|use password PASS" + " for authentication")}, + { oEnvPass, "env-pass", 0, N_("take password from $DIRMNGR_LDAP_PASS")}, + { oDN, "dn", 2, N_("|STRING|query DN STRING")}, + { oFilter, "filter", 2, N_("|STRING|use STRING as filter expression")}, + { oAttr, "attr", 2, N_("|STRING|return the attribute STRING")}, + { oOnlySearchTimeout, "only-search-timeout", 0, "@"}, + { oLogWithPID,"log-with-pid", 0, "@"}, + { 0, NULL, 0, NULL } +}; + + +/* The usual structure for the program flags. */ +static struct +{ + int quiet; + int verbose; + struct timeval timeout; /* Timeout for the LDAP search functions. */ + unsigned int alarm_timeout; /* And for the alarm based timeout. */ + int multi; + + /* Note that we can't use const for the strings because ldap_* are + not defined that way. */ + char *proxy; /* Host and Port override. */ + char *user; /* Authentication user. */ + char *pass; /* Authentication password. */ + char *host; /* Override host. */ + int port; /* Override port. */ + char *dn; /* Override DN. */ + char *filter;/* Override filter. */ + char *attr; /* Override attribute. */ +} opt; + + +/* Prototypes. */ +static void catch_alarm (int dummy); +static int process_url (const char *url); + + + +/* Function called by argparse.c to display information. */ +static const char * +my_strusage (int level) +{ + const char *p; + + switch(level) + { + case 11: p = "dirmngr_ldap (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 49: p = PACKAGE_BUGREPORT; break; + case 1: + case 40: p = + _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n"); + break; + case 41: p = + _("Syntax: dirmngr_ldap [options] [URL]\n" + "Internal LDAP helper for Dirmngr.\n" + "Interface and options may change without notice.\n"); + break; + + default: p = NULL; + } + return p; +} + + +static void +my_i18n_init (void) +{ +#warning Better use common init functions +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file (PACKAGE); +#else +# ifdef ENABLE_NLS + setlocale (LC_ALL, "" ); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +# endif +#endif +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int any_err = 0; + char *p; + int only_search_timeout = 0; + +#ifdef HAVE_W32_SYSTEM + /* Yeah, right. Sigh. */ + _setmode (_fileno (stdout), _O_BINARY); +#endif + + set_strusage (my_strusage); + log_set_prefix ("dirmngr_ldap", JNLIB_LOG_WITH_PREFIX); + + /* Setup I18N. */ + my_i18n_init(); + + /* LDAP defaults */ + opt.timeout.tv_sec = DEFAULT_LDAP_TIMEOUT; + opt.timeout.tv_usec = 0; + opt.alarm_timeout = 0; + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* Do not remove the args. */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oQuiet: opt.quiet++; break; + case oTimeout: + opt.timeout.tv_sec = pargs.r.ret_int; + opt.timeout.tv_usec = 0; + opt.alarm_timeout = pargs.r.ret_int; + break; + case oOnlySearchTimeout: only_search_timeout = 1; break; + case oMulti: opt.multi = 1; break; + case oUser: opt.user = pargs.r.ret_str; break; + case oPass: opt.pass = pargs.r.ret_str; break; + case oEnvPass: + opt.pass = getenv ("DIRMNGR_LDAP_PASS"); + break; + case oProxy: opt.proxy = pargs.r.ret_str; break; + case oHost: opt.host = pargs.r.ret_str; break; + case oPort: opt.port = pargs.r.ret_int; break; + case oDN: opt.dn = pargs.r.ret_str; break; + case oFilter: opt.filter = pargs.r.ret_str; break; + case oAttr: opt.attr = pargs.r.ret_str; break; + case oLogWithPID: + { + unsigned int oldflags; + log_get_prefix (&oldflags); + log_set_prefix (NULL, oldflags | JNLIB_LOG_WITH_PID); + } + break; + + default : pargs.err = 2; break; + } + } + + if (only_search_timeout) + opt.alarm_timeout = 0; + + if (opt.proxy) + { + opt.host = xstrdup (opt.proxy); + p = strchr (opt.host, ':'); + if (p) + { + *p++ = 0; + opt.port = atoi (p); + } + if (!opt.port) + opt.port = 389; /* make sure ports gets overridden. */ + } + + if (opt.port < 0 || opt.port > 65535) + log_error (_("invalid port number %d\n"), opt.port); + + if (log_get_errorcount (0)) + exit (2); + + if (argc < 1) + usage (1); + + if (opt.alarm_timeout) + { +#ifndef HAVE_W32_SYSTEM +# if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION) + struct sigaction act; + + act.sa_handler = catch_alarm; + sigemptyset (&act.sa_mask); + act.sa_flags = 0; + if (sigaction (SIGALRM,&act,NULL)) +# else + if (signal (SIGALRM, catch_alarm) == SIG_ERR) +# endif + log_fatal ("unable to register timeout handler\n"); +#endif + } + + for (; argc; argc--, argv++) + if (process_url (*argv)) + any_err = 1; + + return any_err; +} + + +static void +catch_alarm (int dummy) +{ + (void)dummy; + _exit (10); +} + + +static void +set_timeout (void) +{ +#ifndef HAVE_W32_SYSTEM + /* FIXME for W32. */ + if (opt.alarm_timeout) + alarm (opt.alarm_timeout); +#endif +} + + +/* Helper for fetch_ldap(). */ +static int +print_ldap_entries (LDAP *ld, LDAPMessage *msg, char *want_attr) +{ + LDAPMessage *item; + int any = 0; + + for (item = ldap_first_entry (ld, msg); item; + item = ldap_next_entry (ld, item)) + { + BerElement *berctx; + char *attr; + + if (opt.verbose > 1) + log_info (_("scanning result for attribute `%s'\n"), + want_attr? want_attr : "[all]"); + + if (opt.multi) + { /* Write item marker. */ + if (fwrite ("I\0\0\0\0", 5, 1, stdout) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + return -1; + } + } + + + for (attr = ldap_first_attribute (ld, item, &berctx); attr; + attr = ldap_next_attribute (ld, item, berctx)) + { + struct berval **values; + int idx; + + if (opt.verbose > 1) + log_info (_(" available attribute `%s'\n"), attr); + + set_timeout (); + + /* I case we want only one attribute we do a case + insensitive compare without the optional extension + (i.e. ";binary"). Case insensitive is not really correct + but the best we can do. */ + if (want_attr) + { + char *cp1, *cp2; + int cmpres; + + cp1 = strchr (want_attr, ';'); + if (cp1) + *cp1 = 0; + cp2 = strchr (attr, ';'); + if (cp2) + *cp2 = 0; + cmpres = ascii_strcasecmp (want_attr, attr); + if (cp1) + *cp1 = ';'; + if (cp2) + *cp2 = ';'; + if (cmpres) + { + ldap_memfree (attr); + continue; /* Not found: Try next attribute. */ + } + } + + values = ldap_get_values_len (ld, item, attr); + + if (!values) + { + if (opt.verbose) + log_info (_("attribute `%s' not found\n"), attr); + ldap_memfree (attr); + continue; + } + + if (opt.verbose) + { + log_info (_("found attribute `%s'\n"), attr); + if (opt.verbose > 1) + for (idx=0; values[idx]; idx++) + log_info (" length[%d]=%d\n", + idx, (int)values[0]->bv_len); + + } + + if (opt.multi) + { /* Write attribute marker. */ + unsigned char tmp[5]; + size_t n = strlen (attr); + + tmp[0] = 'A'; + tmp[1] = (n >> 24); + tmp[2] = (n >> 16); + tmp[3] = (n >> 8); + tmp[4] = (n); + if (fwrite (tmp, 5, 1, stdout) != 1 + || fwrite (attr, n, 1, stdout) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + ldap_memfree (attr); + ber_free (berctx, 0); + return -1; + } + } + + for (idx=0; values[idx]; idx++) + { + if (opt.multi) + { /* Write value marker. */ + unsigned char tmp[5]; + size_t n = values[0]->bv_len; + + tmp[0] = 'V'; + tmp[1] = (n >> 24); + tmp[2] = (n >> 16); + tmp[3] = (n >> 8); + tmp[4] = (n); + + if (fwrite (tmp, 5, 1, stdout) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + ldap_memfree (attr); + ber_free (berctx, 0); + return -1; + } + } +#if 1 + /* Note: this does not work for STDOUT on a Windows + console, where it fails with "Not enough space" for + CRLs which are 52 KB or larger. */ + if (fwrite (values[0]->bv_val, values[0]->bv_len, + 1, stdout) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + ldap_memfree (attr); + ber_free (berctx, 0); + return -1; + } +#else + /* On Windows console STDOUT, we have to break up the + writes into small parts. */ + { + int n = 0; + while (n < values[0]->bv_len) + { + int cnt = values[0]->bv_len - n; + /* The actual limit is (52 * 1024 - 1) on Windows XP SP2. */ +#define MAX_CNT (32*1024) + if (cnt > MAX_CNT) + cnt = MAX_CNT; + + if (fwrite (((char *) values[0]->bv_val) + n, cnt, 1, + stdout) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + ldap_memfree (attr); + ber_free (berctx, 0); + return -1; + } + n += cnt; + } + } +#endif + any = 1; + if (!opt.multi) + break; /* Print only the first value. */ + } + ldap_value_free_len (values); + ldap_memfree (attr); + if (want_attr || !opt.multi) + break; /* We only want to return the first attribute. */ + } + ber_free (berctx, 0); + } + + if (opt.verbose > 1 && any) + log_info ("result has been printed\n"); + + return any?0:-1; +} + + + +/* Helper for the URL based LDAP query. */ +static int +fetch_ldap (const char *url, const LDAPURLDesc *ludp) +{ + LDAP *ld; + LDAPMessage *msg; + int rc = 0; + char *host, *dn, *filter, *attrs[2], *attr; + int port; + + host = opt.host? opt.host : ludp->lud_host; + port = opt.port? opt.port : ludp->lud_port; + dn = opt.dn? opt.dn : ludp->lud_dn; + filter = opt.filter? opt.filter : ludp->lud_filter; + attrs[0] = opt.attr? opt.attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL; + attrs[1] = NULL; + attr = attrs[0]; + + if (!port) + port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389; + + if (opt.verbose) + { + log_info (_("processing url `%s'\n"), url); + if (opt.user) + log_info (_(" user `%s'\n"), opt.user); + if (opt.pass) + log_info (_(" pass `%s'\n"), *opt.pass?"*****":""); + if (host) + log_info (_(" host `%s'\n"), host); + log_info (_(" port %d\n"), port); + if (dn) + log_info (_(" DN `%s'\n"), dn); + if (filter) + log_info (_(" filter `%s'\n"), filter); + if (opt.multi && !opt.attr && ludp->lud_attrs) + { + int i; + for (i=0; ludp->lud_attrs[i]; i++) + log_info (_(" attr `%s'\n"), ludp->lud_attrs[i]); + } + else if (attr) + log_info (_(" attr `%s'\n"), attr); + } + + + if (!host || !*host) + { + log_error (_("no host name in `%s'\n"), url); + return -1; + } + if (!opt.multi && !attr) + { + log_error (_("no attribute given for query `%s'\n"), url); + return -1; + } + + if (!opt.multi && !opt.attr + && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1]) + log_info (_("WARNING: using first attribute only\n")); + + + set_timeout (); + ld = ldap_init (host, port); + if (!ld) + { + log_error (_("LDAP init to `%s:%d' failed: %s\n"), + host, port, strerror (errno)); + return -1; + } + if (ldap_simple_bind_s (ld, opt.user, opt.pass)) + { + log_error (_("binding to `%s:%d' failed: %s\n"), + host, port, strerror (errno)); + /* FIXME: Need deinit (ld)? */ + return -1; + } + + set_timeout (); + rc = ldap_search_st (ld, dn, ludp->lud_scope, filter, + opt.multi && !opt.attr && ludp->lud_attrs? + ludp->lud_attrs:attrs, + 0, + &opt.timeout, &msg); + if (rc == LDAP_SIZELIMIT_EXCEEDED && opt.multi) + { + if (fwrite ("E\0\0\0\x09truncated", 14, 1, stdout) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + return -1; + } + } + else if (rc) + { + log_error (_("searching `%s' failed: %s\n"), + url, ldap_err2string (rc)); + if (rc != LDAP_NO_SUCH_OBJECT) + { + /* FIXME: Need deinit (ld)? */ + /* Hmmm: Do we need to released MSG in case of an error? */ + return -1; + } + } + + rc = print_ldap_entries (ld, msg, opt.multi? NULL:attr); + + ldap_msgfree (msg); + /* FIXME: Need deinit (ld)? */ + return rc; +} + + + + +/* Main processing. Take the URL and run the LDAP query. The result + is printed to stdout, errors are logged to the log stream. */ +static int +process_url (const char *url) +{ + int rc; + LDAPURLDesc *ludp = NULL; + + + if (!ldap_is_ldap_url (url)) + { + log_error (_("`%s' is not an LDAP URL\n"), url); + return -1; + } + + if (ldap_url_parse (url, &ludp)) + { + log_error (_("`%s' is an invalid LDAP URL\n"), url); + return -1; + } + + rc = fetch_ldap (url, ludp); + + ldap_free_urldesc (ludp); + return rc; +} + diff --git a/dirmngr/get-path.c b/dirmngr/get-path.c new file mode 100644 index 000000000..c944ec1dd --- /dev/null +++ b/dirmngr/get-path.c @@ -0,0 +1,620 @@ +/* get-path.c - Utility functions for the W32 API + Copyright (C) 1999 Free Software Foundation, Inc + Copyright (C) 2001 Werner Koch (dd9jn) + Copyright (C) 2001, 2002, 2003, 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 */ + +#error Code has been replaced by common/homedir.c + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_W32_SYSTEM +#include +#include +#include +#endif + +#include "util.h" + +#ifdef HAVE_W32_SYSTEM +#define GNUPG_DEFAULT_HOMEDIR "c:/gnupg" +#elif defined(__VMS) +#define GNUPG_DEFAULT_HOMEDIR "/SYS\$LOGIN/gnupg" +#else +#define GNUPG_DEFAULT_HOMEDIR "~/.gnupg" +#endif + +#ifdef HAVE_DOSISH_SYSTEM +#define DIRSEP_C '\\' +#define DIRSEP_S "\\" +#else +#define DIRSEP_C '/' +#define DIRSEP_S "/" +#endif + + +#ifdef HAVE_W32_SYSTEM +#define RTLD_LAZY 0 + +static __inline__ void * +dlopen (const char * name, int flag) +{ + void * hd = LoadLibrary (name); + return hd; +} + +static __inline__ void * +dlsym (void * hd, const char * sym) +{ + if (hd && sym) + { + void * fnc = GetProcAddress (hd, sym); + if (!fnc) + return NULL; + return fnc; + } + return NULL; +} + +static __inline__ int +dlclose (void * hd) +{ + if (hd) + { + FreeLibrary (hd); + return 0; + } + return -1; +} + + +/* Return a string from the W32 Registry or NULL in case of error. + Caller must release the return value. A NULL for root is an alias + for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */ +static char * +read_w32_registry_string (const char *root, const char *dir, const char *name) +{ + HKEY root_key, key_handle; + DWORD n1, nbytes, type; + char *result = NULL; + + if ( !root ) + root_key = HKEY_CURRENT_USER; + else if ( !strcmp( root, "HKEY_CLASSES_ROOT" ) ) + root_key = HKEY_CLASSES_ROOT; + else if ( !strcmp( root, "HKEY_CURRENT_USER" ) ) + root_key = HKEY_CURRENT_USER; + else if ( !strcmp( root, "HKEY_LOCAL_MACHINE" ) ) + root_key = HKEY_LOCAL_MACHINE; + else if ( !strcmp( root, "HKEY_USERS" ) ) + root_key = HKEY_USERS; + else if ( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) ) + root_key = HKEY_PERFORMANCE_DATA; + else if ( !strcmp( root, "HKEY_CURRENT_CONFIG" ) ) + root_key = HKEY_CURRENT_CONFIG; + else + return NULL; + + if ( RegOpenKeyEx ( root_key, dir, 0, KEY_READ, &key_handle ) ) + { + if (root) + return NULL; /* no need for a RegClose, so return direct */ + /* It seems to be common practise to fall back to HKLM. */ + if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) + return NULL; /* still no need for a RegClose, so return direct */ + } + + nbytes = 1; + if ( RegQueryValueEx( key_handle, name, 0, NULL, NULL, &nbytes ) ) + { + if (root) + goto leave; + /* Try to fallback to HKLM also vor a missing value. */ + RegCloseKey (key_handle); + if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) + return NULL; /* Nope. */ + if (RegQueryValueEx ( key_handle, name, 0, NULL, NULL, &nbytes)) + goto leave; + } + result = malloc ( (n1=nbytes+1) ); + if ( !result ) + goto leave; + if ( RegQueryValueEx ( key_handle, name, 0, &type, result, &n1 ) ) + { + free(result); result = NULL; + goto leave; + } + result[nbytes] = 0; /* Make sure it is really a string. */ + if (type == REG_EXPAND_SZ && strchr (result, '%')) + { + char *tmp; + + n1 += 1000; + tmp = malloc (n1+1); + if (!tmp) + goto leave; + nbytes = ExpandEnvironmentStrings (result, tmp, n1); + if (nbytes && nbytes > n1) + { + free (tmp); + n1 = nbytes; + tmp = malloc (n1 + 1); + if (!tmp) + goto leave; + nbytes = ExpandEnvironmentStrings (result, tmp, n1); + if (nbytes && nbytes > n1) { + free (tmp); /* Oops - truncated, better don't expand at all. */ + goto leave; + } + tmp[nbytes] = 0; + free (result); + result = tmp; + } + else if (nbytes) /* Okay, reduce the length. */ + { + tmp[nbytes] = 0; + free (result); + result = malloc (strlen (tmp)+1); + if (!result) + result = tmp; + else + { + strcpy (result, tmp); + free (tmp); + } + } + else /* Error - don't expand. */ + { + free (tmp); + } + } + + leave: + RegCloseKey( key_handle ); + return result; +} + + +/* This is a helper function to load and run a Windows function from + either of one DLLs. */ +static HRESULT +w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e) +{ + static int initialized; + static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR); + + if (!initialized) + { + static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL }; + void *handle; + int i; + + initialized = 1; + + for (i=0, handle = NULL; !handle && dllnames[i]; i++) + { + handle = dlopen (dllnames[i], RTLD_LAZY); + if (handle) + { + func = dlsym (handle, "SHGetFolderPathA"); + if (!func) + { + dlclose (handle); + handle = NULL; + } + } + } + } + + if (func) + return func (a,b,c,d,e); + else + return -1; +} + + +#if 0 +static char * +find_program_in_inst_dir (const char *name) +{ + char *result = NULL; + char *tmp; + + tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE", + "Software\\GNU\\GnuPG", + "Install Directory"); + if (!tmp) + return NULL; + + result = malloc (strlen (tmp) + 1 + strlen (name) + 1); + if (!result) + { + free (tmp); + return NULL; + } + + strcpy (stpcpy (stpcpy (result, tmp), "\\"), name); + free (tmp); + if (access (result, F_OK)) + { + free (result); + return NULL; + } + + return result; +} + + +static char * +find_program_at_standard_place (const char *name) +{ + char path[MAX_PATH]; + char *result = NULL; + + if (w32_shgetfolderpath (NULL, CSIDL_PROGRAM_FILES, NULL, 0, path) >= 0) + { + result = malloc (strlen (path) + 1 + strlen (name) + 1); + if (result) + { + strcpy (stpcpy (stpcpy (result, path), "\\"), name); + if (access (result, F_OK)) + { + free (result); + result = NULL; + } + } + } + return result; +} +#endif +#endif + + +const char * +get_dirmngr_ldap_path (void) +{ + static char *pgmname; + +#ifdef HAVE_W32_SYSTEM + if (! pgmname) + { + const char *dir = dirmngr_libexecdir (); + const char *exe = "\\dirmngr_ldap.exe"; + pgmname = malloc (strlen (dir) + strlen (exe) + 1); + if (pgmname) + strcpy (stpcpy (pgmname, dir), exe); + } +#endif + if (!pgmname) + pgmname = DIRMNGR_LIBEXECDIR "/dirmngr_ldap"; + return pgmname; +} + + + +/* Home directory. */ + +#ifdef HAVE_W32_SYSTEM +#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*/ + +/* 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) + { + 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)) + CreateDirectory (dir, NULL); + } + 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; + + 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, "Software\\GNU\\GnuPG", + "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; + + return dir; +} + + +#ifdef HAVE_W32_SYSTEM +static const char * +w32_rootdir (void) +{ + static int got_dir; + static char dir[MAX_PATH+5]; + + if (!got_dir) + { + char *p; + + if ( !GetModuleFileName ( NULL, dir, MAX_PATH) ) + { + log_debug ("GetModuleFileName failed: %s\n", w32_strerror (0)); + *dir = 0; + } + got_dir = 1; + p = strrchr (dir, DIRSEP_C); + if (p) + *p = 0; + else + { + log_debug ("bad filename `%s' returned for this process\n", dir); + *dir = 0; + } + } + + if (*dir) + return dir; + /* Fallback to the hardwired value. */ + return DIRMNGR_LIBEXECDIR; +} + +static const char * +w32_commondir (void) +{ + static char *dir; + + if (!dir) + { + char path[MAX_PATH]; + + 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 (w32_rootdir ()); + } + } + + return dir; +} +#endif /*HAVE_W32_SYSTEM*/ + + + + +/* 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 * +dirmngr_sysconfdir (void) +{ +#ifdef HAVE_W32_SYSTEM + static char *name; + + if (!name) + { + const char *s1, *s2; + s1 = w32_commondir (); + s2 = DIRSEP_S "etc" DIRSEP_S "dirmngr"; + name = xmalloc (strlen (s1) + strlen (s2) + 1); + strcpy (stpcpy (name, s1), s2); + } + return name; +#else /*!HAVE_W32_SYSTEM*/ + return DIRMNGR_SYSCONFDIR; +#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 * +dirmngr_libexecdir (void) +{ +#ifdef HAVE_W32_SYSTEM + return w32_rootdir (); +#else /*!HAVE_W32_SYSTEM*/ + return DIRMNGR_LIBEXECDIR; +#endif /*!HAVE_W32_SYSTEM*/ +} + + +const char * +dirmngr_datadir (void) +{ +#ifdef HAVE_W32_SYSTEM + static char *name; + + if (!name) + { + const char *s1, *s2; + s1 = w32_commondir (); + s2 = DIRSEP_S "lib" DIRSEP_S "dirmngr"; + name = xmalloc (strlen (s1) + strlen (s2) + 1); + strcpy (stpcpy (name, s1), s2); + } + return name; +#else /*!HAVE_W32_SYSTEM*/ + return DIRMNGR_DATADIR; +#endif /*!HAVE_W32_SYSTEM*/ +} + + +const char * +dirmngr_cachedir (void) +{ +#ifdef HAVE_W32_SYSTEM + static const char *dir; + + if (!dir) + { + char path[MAX_PATH]; + const char *s1[] = { "GNU", "cache", "dirmngr", NULL }; + int s1_len; + const char **comp; + + s1_len = 0; + for (comp = s1; *comp; comp++) + { + /* Take account for the separator. */ + 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)) + CreateDirectory (tmp, NULL); + } + + dir = tmp; + } + else + dir = "c:\\temp\\cache\\dirmngr"; + } + return dir; +#else /*!HAVE_W32_SYSTEM*/ + return DIRMNGR_CACHEDIR; +#endif /*!HAVE_W32_SYSTEM*/ +} + + +const char * +default_socket_name (void) +{ +#ifdef HAVE_W32_SYSTEM + static char *name; + + if (!name) + { + char s1[MAX_PATH]; + const char *s2; + + /* We need something akin CSIDL_COMMON_PROGRAMS, but local + (non-roaming). This is becuase the file needs to be on the + local machine and makes only sense on that machine. + CSIDL_WINDOWS seems to be the only location which guarantees + that. */ + if (w32_shgetfolderpath (NULL, CSIDL_WINDOWS, NULL, 0, s1) < 0) + strcpy (s1, "C:\\WINDOWS"); + s2 = DIRSEP_S "S.dirmngr"; + name = xmalloc (strlen (s1) + strlen (s2) + 1); + strcpy (stpcpy (name, s1), s2); + } + return name; +#else /*!HAVE_W32_SYSTEM*/ + return DIRMNGR_SOCKETDIR "/socket"; +#endif /*!HAVE_W32_SYSTEM*/ +} diff --git a/dirmngr/http.c b/dirmngr/http.c new file mode 100644 index 000000000..b10ba254e --- /dev/null +++ b/dirmngr/http.c @@ -0,0 +1,1861 @@ +/* http.c - HTTP protocol handler + * Copyright (C) 1999, 2001, 2002, 2003, 2004, + * 2006, 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 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. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* Simple HTTP client implementation. We try to keep the code as + self-contained as possible. There are some contraints however: + + - estream is required. We now require estream because it provides a + very useful and portable asprintf implementation and the fopencookie + function. + - stpcpy is required + - fixme: list other requirements. + + + - With HTTP_USE_GNUTLS support for https is provided (this also + requires estream). + - With HTTP_NO_WSASTARTUP the socket initialization is not done + under Windows. This is useful if the socket layer has already + been initialized elsewhere. This also avoids the installation of + an exit handler to cleanup the socket layer. +*/ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_W32_SYSTEM +# include +#else /*!HAVE_W32_SYSTEM*/ +# include +# include +# include +# include +# include +# include +# include +#endif /*!HAVE_W32_SYSTEM*/ + +#include + +#ifdef HTTP_USE_GNUTLS +# include +/* For non-understandable reasons GNUTLS dropped the _t suffix from + all types. yes, ISO-C might be read as this but there are still + other name space conflicts and using _t is actually a Good + Thing. */ +typedef gnutls_session gnutls_session_t; +typedef gnutls_transport_ptr gnutls_transport_ptr_t; +#endif /*HTTP_USE_GNUTLS*/ + +#ifdef TEST +#undef USE_DNS_SRV +#endif + +#include "util.h" +#include "i18n.h" +#include "http.h" +#ifdef USE_DNS_SRV +#include "srv.h" +#else /*!USE_DNS_SRV*/ +/* If we are not compiling with SRV record support we provide stub + data structures. */ +#ifndef MAXDNAME +#define MAXDNAME 1025 +#endif +struct srventry +{ + unsigned short priority; + unsigned short weight; + unsigned short port; + int run_count; + char target[MAXDNAME]; +}; +#endif/*!USE_DNS_SRV*/ + + +#ifdef HAVE_W32_SYSTEM +#define sock_close(a) closesocket(a) +#else +#define sock_close(a) close(a) +#endif + +#ifndef EAGAIN +#define EAGAIN EWOULDBLOCK +#endif + +#define HTTP_PROXY_ENV "http_proxy" +#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */ +#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "01234567890@" \ + "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" + +/* A long counter type. */ +#ifdef HAVE_STRTOULL +typedef unsigned long long longcounter_t; +#define counter_strtoul(a) strtoull ((a), NULL, 10) +#else +typedef unsigned long longcounter_t; +#define counter_strtoul(a) strtoul ((a), NULL, 10) +#endif + +#ifndef HTTP_USE_GNUTLS +typedef void * gnutls_session_t; +#endif + +static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part); +static int remove_escapes (char *string); +static int insert_escapes (char *buffer, const char *string, + const char *special); +static uri_tuple_t parse_tuple (char *string); +static gpg_error_t send_request (http_t hd, + const char *auth, const char *proxy); +static char *build_rel_path (parsed_uri_t uri); +static gpg_error_t parse_response (http_t hd); + +static int connect_server (const char *server, unsigned short port, + unsigned int flags, const char *srvtag); + +static ssize_t cookie_read (void *cookie, void *buffer, size_t size); +static ssize_t cookie_write (void *cookie, const void *buffer, size_t size); +static int cookie_close (void *cookie); + +static es_cookie_io_functions_t cookie_functions = + { + cookie_read, + cookie_write, + NULL, + cookie_close + }; + +struct cookie_s +{ + int fd; /* File descriptor or -1 if already closed. */ + gnutls_session_t tls_session; /* TLS session context or NULL if not used. */ + + /* The remaining content length and a flag telling whether to use + the content length. */ + longcounter_t content_length; + unsigned int content_length_valid:1; + + /* Flag to communicate with the close handler. */ + unsigned int keep_socket:1; +}; +typedef struct cookie_s *cookie_t; + + +#ifdef HTTP_USE_GNUTLS +static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int); +#endif /*HTTP_USE_GNUTLS*/ + + +/* An object to save header lines. */ +struct header_s +{ + struct header_s *next; + char *value; /* The value of the header (malloced). */ + char name[1]; /* The name of the header (canonicalized). */ +}; +typedef struct header_s *header_t; + + +/* Our handle context. */ +struct http_context_s +{ + unsigned int status_code; + int sock; + unsigned int in_data:1; + unsigned int is_http_0_9:1; + estream_t fp_read; + estream_t fp_write; + void *write_cookie; + void *read_cookie; + void *tls_context; + parsed_uri_t uri; + http_req_t req_type; + char *buffer; /* Line buffer. */ + size_t buffer_size; + unsigned int flags; + header_t headers; /* Received headers. */ +}; + + + + +#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP) + +#if GNUPG_MAJOR_VERSION == 1 +#define REQ_WINSOCK_MAJOR 1 +#define REQ_WINSOCK_MINOR 1 +#else +#define REQ_WINSOCK_MAJOR 2 +#define REQ_WINSOCK_MINOR 2 +#endif + + +static void +deinit_sockets (void) +{ + WSACleanup(); +} + +static void +init_sockets (void) +{ + static int initialized; + static WSADATA wsdata; + + if (initialized) + return; + + if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) ) + { + log_error ("error initializing socket library: ec=%d\n", + (int)WSAGetLastError () ); + return; + } + if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR + || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR ) + { + log_error ("socket library version is %x.%x - but %d.%d needed\n", + LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion), + REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR); + WSACleanup(); + return; + } + atexit ( deinit_sockets ); + initialized = 1; +} +#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/ + + + +/* + * Helper function to create an HTTP header with hex encoded data. A + * new buffer is returned. This buffer is the concatenation of the + * string PREFIX, the hex-encoded DATA of length LEN and the string + * SUFFIX. On error NULL is returned and ERRNO set. + */ +static char * +make_header_line (const char *prefix, const char *suffix, + const void *data, size_t len ) +{ + static unsigned char bintoasc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + const unsigned int *s = data; + char *buffer, *p; + + buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1); + if (!buffer) + return NULL; + p = stpcpy (buffer, prefix); + for ( ; len >= 3 ; len -= 3, s += 3 ) + { + *p++ = bintoasc[(s[0] >> 2) & 077]; + *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; + *p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077]; + *p++ = bintoasc[s[2]&077]; + } + if ( len == 2 ) + { + *p++ = bintoasc[(s[0] >> 2) & 077]; + *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; + *p++ = bintoasc[((s[1]<<2)&074)]; + *p++ = '='; + } + else if ( len == 1 ) + { + *p++ = bintoasc[(s[0] >> 2) & 077]; + *p++ = bintoasc[(s[0] <<4)&060]; + *p++ = '='; + *p++ = '='; + } + strcpy (p, suffix); + return buffer; +} + + + + +void +http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) ) +{ +#ifdef HTTP_USE_GNUTLS + tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb; +#else + (void)cb; +#endif +} + + + +/* Start a HTTP retrieval and return on success in R_HD a context + pointer for completing the the request and to wait for the + response. */ +gpg_error_t +http_open (http_t *r_hd, http_req_t reqtype, const char *url, + const char *auth, unsigned int flags, const char *proxy, + void *tls_context) +{ + gpg_error_t err; + http_t hd; + + *r_hd = NULL; + + if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST)) + return gpg_error (GPG_ERR_INV_ARG); + + /* Make need_header default unless ignore_cl is set. We might want + to drop the need_header entirely. */ + if (!(flags & HTTP_FLAG_IGNORE_CL)) + flags |= HTTP_FLAG_NEED_HEADER; + + /* Create the handle. */ + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return gpg_error_from_syserror (); + hd->sock = -1; + hd->req_type = reqtype; + hd->flags = flags; + hd->tls_context = tls_context; + + err = http_parse_uri (&hd->uri, url); + if (!err) + err = send_request (hd, auth, proxy); + + if (err) + { + if (!hd->fp_read && !hd->fp_write && hd->sock != -1) + sock_close (hd->sock); + if (hd->fp_read) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + http_release_parsed_uri (hd->uri); + xfree (hd); + } + else + *r_hd = hd; + return err; +} + + +void +http_start_data (http_t hd) +{ + if (!hd->in_data) + { + es_fputs ("\r\n", hd->fp_write); + es_fflush (hd->fp_write); + hd->in_data = 1; + } + else + es_fflush (hd->fp_write); +} + + +gpg_error_t +http_wait_response (http_t hd) +{ + gpg_error_t err; + cookie_t cookie; + + /* Make sure that we are in the data. */ + http_start_data (hd); + + cookie = hd->write_cookie; + if (!cookie) + return gpg_error (GPG_ERR_INTERNAL); + + /* Close the write stream but keep the socket open. */ + cookie->keep_socket = 1; + es_fclose (hd->fp_write); + hd->fp_write = NULL; + hd->write_cookie = NULL; + + /* Shutdown one end of the socket is desired. As per HTTP/1.0 this + is not required but some very old servers (e.g. the original pksd + key server didn't worked without it. */ + if ((hd->flags & HTTP_FLAG_SHUTDOWN)) + shutdown (hd->sock, 1); + hd->in_data = 0; + + /* Create a new cookie and a stream for reading. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_error_from_syserror (); + cookie->fd = hd->sock; + if (hd->uri->use_tls) + cookie->tls_session = hd->tls_context; + + hd->read_cookie = cookie; + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + xfree (cookie); + hd->read_cookie = NULL; + return gpg_error_from_syserror (); + } + + err = parse_response (hd); + return err; +} + + +/* Convenience function to send a request and wait for the response. + Closes the handle on error. If PROXY is not NULL, this value will + be used as an HTTP proxy and any enabled $http_proxy gets + ignored. */ +gpg_error_t +http_open_document (http_t *r_hd, const char *document, + const char *auth, unsigned int flags, const char *proxy, + void *tls_context) +{ + gpg_error_t err; + + err = http_open (r_hd, HTTP_REQ_GET, document, auth, flags, + proxy, tls_context); + if (err) + return err; + + err = http_wait_response (*r_hd); + if (err) + http_close (*r_hd, 0); + + return err; +} + + +void +http_close (http_t hd, int keep_read_stream) +{ + if (!hd) + return; + if (!hd->fp_read && !hd->fp_write && hd->sock != -1) + sock_close (hd->sock); + if (hd->fp_read && !keep_read_stream) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + http_release_parsed_uri (hd->uri); + while (hd->headers) + { + header_t tmp = hd->headers->next; + xfree (hd->headers->value); + xfree (hd->headers); + hd->headers = tmp; + } + xfree (hd->buffer); + xfree (hd); +} + + +estream_t +http_get_read_ptr (http_t hd) +{ + return hd?hd->fp_read:NULL; +} + +estream_t +http_get_write_ptr (http_t hd) +{ + return hd?hd->fp_write:NULL; +} + +unsigned int +http_get_status_code (http_t hd) +{ + return hd?hd->status_code:0; +} + + + +/* + * Parse an URI and put the result into the newly allocated RET_URI. + * The caller must always use release_parsed_uri() to releases the + * resources (even on error). + */ +gpg_error_t +http_parse_uri (parsed_uri_t * ret_uri, const char *uri) +{ + *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri)); + strcpy ((*ret_uri)->buffer, uri); + return do_parse_uri (*ret_uri, 0); +} + +void +http_release_parsed_uri (parsed_uri_t uri) +{ + if (uri) + { + uri_tuple_t r, r2; + + for (r = uri->query; r; r = r2) + { + r2 = r->next; + xfree (r); + } + xfree (uri); + } +} + + +static gpg_error_t +do_parse_uri (parsed_uri_t uri, int only_local_part) +{ + uri_tuple_t *tail; + char *p, *p2, *p3, *pp; + int n; + + p = uri->buffer; + n = strlen (uri->buffer); + + /* Initialize all fields to an empty string or an empty list. */ + uri->scheme = uri->host = uri->path = p + n; + uri->port = 0; + uri->params = uri->query = NULL; + uri->use_tls = 0; + + /* A quick validity check. */ + if (strspn (p, VALID_URI_CHARS) != n) + return gpg_error (GPG_ERR_BAD_URI); /* Invalid characters found. */ + + if (!only_local_part) + { + /* Find the scheme. */ + if (!(p2 = strchr (p, ':')) || p2 == p) + return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */ + *p2++ = 0; + for (pp=p; *pp; pp++) + *pp = tolower (*(unsigned char*)pp); + uri->scheme = p; + if (!strcmp (uri->scheme, "http")) + uri->port = 80; +#ifdef HTTP_USE_GNUTLS + else if (!strcmp (uri->scheme, "https")) + { + uri->port = 443; + uri->use_tls = 1; + } +#endif + else + return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */ + + p = p2; + + /* Find the hostname */ + if (*p != '/') + return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */ + + p++; + if (*p == '/') /* There seems to be a hostname. */ + { + p++; + if ((p2 = strchr (p, '/'))) + *p2++ = 0; + + /* Check for username/password encoding */ + if ((p3 = strchr (p, '@'))) + { + uri->auth = p; + *p3++ = '\0'; + p = p3; + } + + for (pp=p; *pp; pp++) + *pp = tolower (*(unsigned char*)pp); + uri->host = p; + if ((p3 = strchr (p, ':'))) + { + *p3++ = 0; + uri->port = atoi (p3); + } + + uri->host = p; + if ((n = remove_escapes (uri->host)) < 0) + return gpg_error (GPG_ERR_BAD_URI); + if (n != strlen (p)) + return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */ + p = p2 ? p2 : NULL; + } + } /* End global URI part. */ + + /* Parse the pathname part */ + if (!p || !*p) + return 0; /* We don't have a path. Okay. */ + + /* TODO: Here we have to check params. */ + + /* Do we have a query part? */ + if ((p2 = strchr (p, '?'))) + *p2++ = 0; + + uri->path = p; + if ((n = remove_escapes (p)) < 0) + return gpg_error (GPG_ERR_BAD_URI); + if (n != strlen (p)) + return gpg_error (GPG_ERR_BAD_URI); /* Path includes a Nul. */ + p = p2 ? p2 : NULL; + + if (!p || !*p) + return 0; /* We don't have a query string. Okay. */ + + /* Now parse the query string. */ + tail = &uri->query; + for (;;) + { + uri_tuple_t elem; + + if ((p2 = strchr (p, '&'))) + *p2++ = 0; + if (!(elem = parse_tuple (p))) + return gpg_error (GPG_ERR_BAD_URI); + *tail = elem; + tail = &elem->next; + + if (!p2) + break; /* Ready. */ + p = p2; + } + + return 0; +} + + +/* + * Remove all %xx escapes; this is done in-place. Returns: New length + * of the string. + */ +static int +remove_escapes (char *string) +{ + int n = 0; + unsigned char *p, *s; + + for (p = s = (unsigned char*)string; *s; s++) + { + if (*s == '%') + { + if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2])) + { + s++; + *p = *s >= '0' && *s <= '9' ? *s - '0' : + *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; + *p <<= 4; + s++; + *p |= *s >= '0' && *s <= '9' ? *s - '0' : + *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; + p++; + n++; + } + else + { + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p = 0; + return -1; /* Bad URI. */ + } + } + else + { + *p++ = *s; + n++; + } + } + *p = 0; /* Make sure to keep a string terminator. */ + return n; +} + + +static int +insert_escapes (char *buffer, const char *string, + const char *special) +{ + const unsigned char *s = (const unsigned char*)string; + int n = 0; + + for (; *s; s++) + { + if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s)) + { + if (buffer) + *(unsigned char*)buffer++ = *s; + n++; + } + else + { + if (buffer) + { + sprintf (buffer, "%%%02X", *s); + buffer += 3; + } + n += 3; + } + } + return n; +} + + +/* Allocate a new string from STRING using standard HTTP escaping as + well as escaping of characters given in SPECIALS. A common pattern + for SPECIALS is "%;?&=". However it depends on the needs, for + example "+" and "/: often needs to be escaped too. Returns NULL on + failure and sets ERRNO. */ +char * +http_escape_string (const char *string, const char *specials) +{ + int n; + char *buf; + + n = insert_escapes (NULL, string, specials); + buf = xtrymalloc (n+1); + if (buf) + { + insert_escapes (buf, string, specials); + buf[n] = 0; + } + return buf; +} + + + +static uri_tuple_t +parse_tuple (char *string) +{ + char *p = string; + char *p2; + int n; + uri_tuple_t tuple; + + if ((p2 = strchr (p, '='))) + *p2++ = 0; + if ((n = remove_escapes (p)) < 0) + return NULL; /* Bad URI. */ + if (n != strlen (p)) + return NULL; /* Name with a Nul in it. */ + tuple = xtrycalloc (1, sizeof *tuple); + if (!tuple) + return NULL; /* Out of core. */ + tuple->name = p; + if (!p2) /* We have only the name, so we assume an empty value string. */ + { + tuple->value = p + strlen (p); + tuple->valuelen = 0; + tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */ + } + else /* Name and value. */ + { + if ((n = remove_escapes (p2)) < 0) + { + xfree (tuple); + return NULL; /* Bad URI. */ + } + tuple->value = p2; + tuple->valuelen = n; + } + return tuple; +} + + +/* + * Send a HTTP request to the server + * Returns 0 if the request was successful + */ +static gpg_error_t +send_request (http_t hd, const char *auth, const char *proxy) +{ + gnutls_session_t tls_session; + gpg_error_t err; + const char *server; + char *request, *p; + unsigned short port; + const char *http_proxy = NULL; + char *proxy_authstr = NULL; + char *authstr = NULL; + int save_errno; + cookie_t cookie; + + + tls_session = hd->tls_context; + if (hd->uri->use_tls && !tls_session) + { + log_error ("TLS requested but no GNUTLS context provided\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + + server = *hd->uri->host ? hd->uri->host : "localhost"; + port = hd->uri->port ? hd->uri->port : 80; + + if ( (proxy && *proxy) + || ( (hd->flags & HTTP_FLAG_TRY_PROXY) + && (http_proxy = getenv (HTTP_PROXY_ENV)) + && *http_proxy )) + { + parsed_uri_t uri; + + if (proxy) + http_proxy = proxy; + + err = http_parse_uri (&uri, http_proxy); + if (err) + { + log_error ("invalid HTTP proxy (%s): %s\n", + http_proxy, gpg_strerror (err)); + http_release_parsed_uri (uri); + return gpg_error (GPG_ERR_CONFIGURATION); + + } + + if (uri->auth) + { + remove_escapes (uri->auth); + proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + uri->auth, strlen(uri->auth)); + if (!proxy_authstr) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (uri); + return err; + } + } + + hd->sock = connect_server (*uri->host ? uri->host : "localhost", + uri->port ? uri->port : 80, + hd->flags, hd->uri->scheme); + save_errno = errno; + http_release_parsed_uri (uri); + } + else + { + hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme); + save_errno = errno; + } + + if (hd->sock == -1) + { + xfree (proxy_authstr); + return (save_errno + ? gpg_error_from_errno (save_errno) + : gpg_error (GPG_ERR_NOT_FOUND)); + } + +#ifdef HTTP_USE_GNUTLS + if (hd->uri->use_tls) + { + int rc; + + gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock); + do + { + rc = gnutls_handshake (tls_session); + } + while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN); + if (rc < 0) + { + log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); + xfree (proxy_authstr); + return gpg_error (GPG_ERR_NETWORK); + } + + if (tls_callback) + { + err = tls_callback (hd, tls_session, 0); + if (err) + { + log_info ("TLS connection authentication failed: %s\n", + gpg_strerror (err)); + xfree (proxy_authstr); + return err; + } + } + } +#endif /*HTTP_USE_GNUTLS*/ + + if (auth || hd->uri->auth) + { + char *myauth; + + if (auth) + { + myauth = xtrystrdup (auth); + if (!myauth) + { + xfree (proxy_authstr); + return gpg_error_from_syserror (); + } + remove_escapes (myauth); + } + else + { + remove_escapes (hd->uri->auth); + myauth = hd->uri->auth; + } + + authstr = make_header_line ("Authorization: Basic %s", "\r\n", + myauth, strlen (myauth)); + if (auth) + xfree (myauth); + + if (!authstr) + { + xfree (proxy_authstr); + return gpg_error_from_syserror (); + } + } + + p = build_rel_path (hd->uri); + if (!p) + return gpg_error_from_syserror (); + + if (http_proxy && *http_proxy) + { + request = es_asprintf + ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + server, port, *p == '/' ? "" : "/", p, + authstr ? authstr : "", + proxy_authstr ? proxy_authstr : ""); + } + else + { + char portstr[35]; + + if (port == 80) + *portstr = 0; + else + snprintf (portstr, sizeof portstr, ":%u", port); + + request = es_asprintf + ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + *p == '/' ? "" : "/", p, server, portstr, + authstr? authstr:""); + } + xfree (p); + if (!request) + { + err = gpg_error_from_syserror (); + xfree (authstr); + xfree (proxy_authstr); + return err; + } + + /* First setup estream so that we can write even the first line + using estream. This is also required for the sake of gnutls. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_error_from_syserror (); + goto leave; + } + cookie->fd = hd->sock; + hd->write_cookie = cookie; + if (hd->uri->use_tls) + cookie->tls_session = tls_session; + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + xfree (cookie); + hd->write_cookie = NULL; + err = gpg_error_from_syserror (); + } + else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + err = gpg_error_from_syserror (); + else + err = 0; + + leave: + es_free (request); + xfree (authstr); + xfree (proxy_authstr); + + return err; +} + + +/* + * Build the relative path from the parsed URI. Minimal + * implementation. May return NULL in case of memory failure; errno + * is then set accordingly. + */ +static char * +build_rel_path (parsed_uri_t uri) +{ + uri_tuple_t r; + char *rel_path, *p; + int n; + + /* Count the needed space. */ + n = insert_escapes (NULL, uri->path, "%;?&"); + /* TODO: build params. */ + for (r = uri->query; r; r = r->next) + { + n++; /* '?'/'&' */ + n += insert_escapes (NULL, r->name, "%;?&="); + if (!r->no_value) + { + n++; /* '=' */ + n += insert_escapes (NULL, r->value, "%;?&="); + } + } + n++; + + /* Now allocate and copy. */ + p = rel_path = xtrymalloc (n); + if (!p) + return NULL; + n = insert_escapes (p, uri->path, "%;?&"); + p += n; + /* TODO: add params. */ + for (r = uri->query; r; r = r->next) + { + *p++ = r == uri->query ? '?' : '&'; + n = insert_escapes (p, r->name, "%;?&="); + p += n; + if (!r->no_value) + { + *p++ = '='; + /* TODO: Use valuelen. */ + n = insert_escapes (p, r->value, "%;?&="); + p += n; + } + } + *p = 0; + return rel_path; +} + + +/* Transform a header name into a standard capitalized format; e.g. + "Content-Type". Conversion stops at the colon. As usual we don't + use the localized versions of ctype.h. */ +static void +capitalize_header_name (char *name) +{ + int first = 1; + + for (; *name && *name != ':'; name++) + { + if (*name == '-') + first = 1; + else if (first) + { + if (*name >= 'a' && *name <= 'z') + *name = *name - 'a' + 'A'; + first = 0; + } + else if (*name >= 'A' && *name <= 'Z') + *name = *name - 'A' + 'a'; + } +} + + +/* Store an HTTP header line in LINE away. Line continuation is + supported as well as merging of headers with the same name. This + function may modify LINE. */ +static gpg_error_t +store_header (http_t hd, char *line) +{ + size_t n; + char *p, *value; + header_t h; + + n = strlen (line); + if (n && line[n-1] == '\n') + { + line[--n] = 0; + if (n && line[n-1] == '\r') + line[--n] = 0; + } + if (!n) /* we are never called to hit this. */ + return gpg_error (GPG_ERR_BUG); + if (*line == ' ' || *line == '\t') + { + /* Continuation. This won't happen too often as it is not + recommended. We use a straightforward implementaion. */ + if (!hd->headers) + return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); + n += strlen (hd->headers->value); + p = xtrymalloc (n+1); + if (!p) + return gpg_error_from_syserror (); + strcpy (stpcpy (p, hd->headers->value), line); + xfree (hd->headers->value); + hd->headers->value = p; + return 0; + } + + capitalize_header_name (line); + p = strchr (line, ':'); + if (!p) + return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); + *p++ = 0; + while (*p == ' ' || *p == '\t') + p++; + value = p; + + for (h=hd->headers; h; h = h->next) + if ( !strcmp (h->name, line) ) + break; + if (h) + { + /* We have already seen a line with that name. Thus we assume + it is a comma separated list and merge them. */ + p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1); + if (!p) + return gpg_error_from_syserror (); + strcpy (stpcpy (stpcpy (p, h->value), ","), value); + xfree (h->value); + h->value = p; + return 0; + } + + /* Append a new header. */ + h = xtrymalloc (sizeof *h + strlen (line)); + if (!h) + return gpg_error_from_syserror (); + strcpy (h->name, line); + h->value = xtrymalloc (strlen (value)+1); + if (!h->value) + { + xfree (h); + return gpg_error_from_syserror (); + } + strcpy (h->value, value); + h->next = hd->headers; + hd->headers = h; + + return 0; +} + + +/* Return the header NAME from the last response. The returned value + is valid as along as HD has not been closed and no othe request has + been send. If the header was not found, NULL is returned. Name + must be canonicalized, that is the first letter of each dash + delimited part must be uppercase and all other letters lowercase. + Note that the context must have been opened with the + HTTP_FLAG_NEED_HEADER. */ +const char * +http_get_header (http_t hd, const char *name) +{ + header_t h; + + for (h=hd->headers; h; h = h->next) + if ( !strcmp (h->name, name) ) + return h->value; + return NULL; +} + + + +/* + * Parse the response from a server. + * Returns: Errorcode and sets some files in the handle + */ +static gpg_error_t +parse_response (http_t hd) +{ + char *line, *p, *p2; + size_t maxlen, len; + cookie_t cookie = hd->read_cookie; + const char *s; + + /* Delete old header lines. */ + while (hd->headers) + { + header_t tmp = hd->headers->next; + xfree (hd->headers->value); + xfree (hd->headers); + hd->headers = tmp; + } + + /* Wait for the status line. */ + do + { + maxlen = MAX_LINELEN; + len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); + line = hd->buffer; + if (!line) + return gpg_error_from_syserror (); /* Out of core. */ + if (!maxlen) + return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */ + if (!len) + return gpg_error (GPG_ERR_EOF); + if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) + log_info ("RESP: `%.*s'\n", + (int)strlen(line)-(*line&&line[1]?2:0),line); + } + while (!*line); + + if ((p = strchr (line, '/'))) + *p++ = 0; + if (!p || strcmp (line, "HTTP")) + return 0; /* Assume http 0.9. */ + + if ((p2 = strpbrk (p, " \t"))) + { + *p2++ = 0; + p2 += strspn (p2, " \t"); + } + if (!p2) + return 0; /* Also assume http 0.9. */ + p = p2; + /* TODO: Add HTTP version number check. */ + if ((p2 = strpbrk (p, " \t"))) + *p2++ = 0; + if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1]) + || !isdigit ((unsigned int)p[2]) || p[3]) + { + /* Malformed HTTP status code - assume http 0.9. */ + hd->is_http_0_9 = 1; + hd->status_code = 200; + return 0; + } + hd->status_code = atoi (p); + + /* Skip all the header lines and wait for the empty line. */ + do + { + maxlen = MAX_LINELEN; + len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); + line = hd->buffer; + if (!line) + return gpg_error_from_syserror (); /* Out of core. */ + /* Note, that we can silently ignore truncated lines. */ + if (!len) + return gpg_error (GPG_ERR_EOF); + /* Trim line endings of empty lines. */ + if ((*line == '\r' && line[1] == '\n') || *line == '\n') + *line = 0; + if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) + log_info ("RESP: `%.*s'\n", + (int)strlen(line)-(*line&&line[1]?2:0),line); + if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line ) + { + gpg_error_t err = store_header (hd, line); + if (err) + return err; + } + } + while (len && *line); + + cookie->content_length_valid = 0; + if (!(hd->flags & HTTP_FLAG_IGNORE_CL)) + { + s = http_get_header (hd, "Content-Length"); + if (s) + { + cookie->content_length_valid = 1; + cookie->content_length = counter_strtoul (s); + } + } + + return 0; +} + +#if 0 +static int +start_server () +{ + struct sockaddr_in mya; + struct sockaddr_in peer; + int fd, client; + fd_set rfds; + int addrlen; + int i; + + if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1) + { + log_error ("socket() failed: %s\n", strerror (errno)); + return -1; + } + i = 1; + if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i))) + log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); + + mya.sin_family = AF_INET; + memset (&mya.sin_addr, 0, sizeof (mya.sin_addr)); + mya.sin_port = htons (11371); + + if (bind (fd, (struct sockaddr *) &mya, sizeof (mya))) + { + log_error ("bind to port 11371 failed: %s\n", strerror (errno)); + sock_close (fd); + return -1; + } + + if (listen (fd, 5)) + { + log_error ("listen failed: %s\n", strerror (errno)); + sock_close (fd); + return -1; + } + + for (;;) + { + FD_ZERO (&rfds); + FD_SET (fd, &rfds); + + if (select (fd + 1, &rfds, NULL, NULL, NULL) <= 0) + continue; /* ignore any errors */ + + if (!FD_ISSET (fd, &rfds)) + continue; + + addrlen = sizeof peer; + client = accept (fd, (struct sockaddr *) &peer, &addrlen); + if (client == -1) + continue; /* oops */ + + log_info ("connect from %s\n", inet_ntoa (peer.sin_addr)); + + fflush (stdout); + fflush (stderr); + if (!fork ()) + { + int c; + FILE *fp; + + fp = fdopen (client, "r"); + while ((c = getc (fp)) != EOF) + putchar (c); + fclose (fp); + exit (0); + } + sock_close (client); + } + + + return 0; +} +#endif + +/* Actually connect to a server. Returns the file descripto or -1 on + error. ERRNO is set on error. */ +static int +connect_server (const char *server, unsigned short port, + unsigned int flags, const char *srvtag) +{ + int sock = -1; + int srvcount = 0; + int hostfound = 0; + int srv, connected; + int last_errno = 0; + struct srventry *serverlist = NULL; + +#ifdef HAVE_W32_SYSTEM + unsigned long inaddr; + +#ifndef HTTP_NO_WSASTARTUP + init_sockets (); +#endif + /* Win32 gethostbyname doesn't handle IP addresses internally, so we + try inet_addr first on that platform only. */ + inaddr = inet_addr(server); + if ( inaddr != INADDR_NONE ) + { + struct sockaddr_in addr; + + memset(&addr,0,sizeof(addr)); + + sock = socket(AF_INET,SOCK_STREAM,0); + if ( sock==INVALID_SOCKET ) + { + log_error("error creating socket: ec=%d\n",(int)WSAGetLastError()); + return -1; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr)); + + if (!connect (sock,(struct sockaddr *)&addr,sizeof(addr)) ) + return sock; + sock_close(sock); + return -1; + } +#endif /*HAVE_W32_SYSTEM*/ + +#ifdef USE_DNS_SRV + /* Do the SRV thing */ + if ((flags & HTTP_FLAG_TRY_SRV) && srvtag) + { + /* We're using SRV, so append the tags. */ + if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME) + { + char srvname[MAXDNAME]; + + stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag), + "._tcp."), server); + srvcount = getsrv (srvname, &serverlist); + } + } +#else /*!USE_DNS_SRV*/ + (void)flags; + (void)srvtag; +#endif /*!USE_DNS_SRV*/ + + if (!serverlist) + { + /* Either we're not using SRV, or the SRV lookup failed. Make + up a fake SRV record. */ + serverlist = xtrycalloc (1, sizeof *serverlist); + if (!serverlist) + return -1; /* Out of core. */ + serverlist->port = port; + strncpy (serverlist->target, server, MAXDNAME); + serverlist->target[MAXDNAME-1] = '\0'; + srvcount = 1; + } + +#ifdef HAVE_GETADDRINFO + connected = 0; + for (srv=0; srv < srvcount && !connected; srv++) + { + struct addrinfo hints, *res, *ai; + char portstr[35]; + + sprintf (portstr, "%hu", port); + memset (&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res)) + continue; /* Not found - try next one. */ + hostfound = 1; + + for (ai = res; ai && !connected; ai = ai->ai_next) + { + if (sock != -1) + sock_close (sock); + sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) + { + int save_errno = errno; + log_error ("error creating socket: %s\n", strerror (errno)); + freeaddrinfo (res); + xfree (serverlist); + errno = save_errno; + return -1; + } + + if (connect (sock, ai->ai_addr, ai->ai_addrlen)) + last_errno = errno; + else + connected = 1; + } + freeaddrinfo (res); + } +#else /* !HAVE_GETADDRINFO */ + connected = 0; + for (srv=0; srv < srvcount && !connected; srv++) + { + int i; + struct hostent *host = NULL; + struct sockaddr_in addr; + + /* Note: This code is not thread-safe. */ + + memset (&addr, 0, sizeof (addr)); + host = gethostbyname (serverlist[srv].target); + if (!host) + continue; + hostfound = 1; + + if (sock != -1) + sock_close (sock); + sock = socket (host->h_addrtype, SOCK_STREAM, 0); + if (sock == -1) + { + log_error (_("error creating socket: %s\n"), strerror (errno)); + xfree (serverlist); + return -1; + } + + addr.sin_family = host->h_addrtype; + if (addr.sin_family != AF_INET) + { + log_error ("unknown address family for `%s'\n", + serverlist[srv].target); + xfree (serverlist); + return -1; + } + addr.sin_port = htons (serverlist[srv].port); + if (host->h_length != 4) + { + log_error ("illegal address length for `%s'\n", + serverlist[srv].target); + xfree (serverlist); + return -1; + } + + /* Try all A records until one responds. */ + for (i = 0; host->h_addr_list[i] && !connected; i++) + { + memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length); + if (connect (sock, (struct sockaddr *) &addr, sizeof (addr))) + last_errno = errno; + else + { + connected = 1; + break; + } + } + } +#endif /* !HAVE_GETADDRINFO */ + + xfree (serverlist); + + if (!connected) + { +#ifdef HAVE_W32_SYSTEM + log_error ("can't connect to `%s': %s%sec=%d\n", + server, + hostfound? "":_("host not found"), + hostfound? "":" - ", (int)WSAGetLastError()); +#else + log_error ("can't connect to `%s': %s\n", + server, + hostfound? strerror (last_errno):"host not found"); +#endif + if (sock != -1) + sock_close (sock); + errno = last_errno; + return -1; + } + return sock; +} + + + + +/* Read handler for estream. */ +static ssize_t +cookie_read (void *cookie, void *buffer, size_t size) +{ + cookie_t c = cookie; + int nread; + + if (c->content_length_valid) + { + if (!c->content_length) + return 0; /* EOF */ + if (c->content_length < size) + size = c->content_length; + } + +#ifdef HTTP_USE_GNUTLS + if (c->tls_session) + { + again: + nread = gnutls_record_recv (c->tls_session, buffer, size); + if (nread < 0) + { + if (nread == GNUTLS_E_INTERRUPTED) + goto again; + if (nread == GNUTLS_E_AGAIN) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + select (0, NULL, NULL, NULL, &tv); + goto again; + } + if (nread == GNUTLS_E_REHANDSHAKE) + goto again; /* A client is allowed to just ignore this request. */ + log_info ("TLS network read failed: %s\n", gnutls_strerror (nread)); + errno = EIO; + return -1; + } + } + else +#endif /*HTTP_USE_GNUTLS*/ + { + do + { + nread = pth_read (c->fd, buffer, size); + } + while (nread == -1 && errno == EINTR); + } + + if (c->content_length_valid && nread > 0) + { + if (nread < c->content_length) + c->content_length -= nread; + else + c->content_length = 0; + } + + return nread; +} + +/* Write handler for estream. */ +static ssize_t +cookie_write (void *cookie, const void *buffer, size_t size) +{ + cookie_t c = cookie; + int nwritten = 0; + +#ifdef HTTP_USE_GNUTLS + if (c->tls_session) + { + int nleft = size; + while (nleft > 0) + { + nwritten = gnutls_record_send (c->tls_session, buffer, nleft); + if (nwritten <= 0) + { + if (nwritten == GNUTLS_E_INTERRUPTED) + continue; + if (nwritten == GNUTLS_E_AGAIN) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + select (0, NULL, NULL, NULL, &tv); + continue; + } + log_info ("TLS network write failed: %s\n", + gnutls_strerror (nwritten)); + errno = EIO; + return -1; + } + nleft -= nwritten; + buffer += nwritten; + } + } + else +#endif /*HTTP_USE_GNUTLS*/ + { + do + { + nwritten = pth_write (c->fd, buffer, size); + } + while (nwritten == -1 && errno == EINTR); + } + + return nwritten; +} + +/* Close handler for estream. */ +static int +cookie_close (void *cookie) +{ + cookie_t c = cookie; + + if (!c) + return 0; + +#ifdef HTTP_USE_GNUTLS + if (c->tls_session && !c->keep_socket) + { + /* This indicates that the read end has been closed. */ + gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR); + } +#endif /*HTTP_USE_GNUTLS*/ + if (c->fd != -1 && !c->keep_socket) + sock_close (c->fd); + + xfree (c); + return 0; +} + + + + +/**** Test code ****/ +#ifdef TEST + +static gpg_error_t +verify_callback (http_t hd, void *tls_context, int reserved) +{ + log_info ("verification of certificates skipped\n"); + return 0; +} + + + +/* static void */ +/* my_gnutls_log (int level, const char *text) */ +/* { */ +/* fprintf (stderr, "gnutls:L%d: %s", level, text); */ +/* } */ + +int +main (int argc, char **argv) +{ + int rc; + parsed_uri_t uri; + uri_tuple_t r; + http_t hd; + int c; + gnutls_session_t tls_session = NULL; +#ifdef HTTP_USE_GNUTLS + gnutls_certificate_credentials certcred; + const int certprio[] = { GNUTLS_CRT_X509, 0 }; +#endif /*HTTP_USE_GNUTLS*/ + header_t hdr; + + es_init (); + log_set_prefix ("http-test", 1 | 4); + if (argc == 1) + { + /*start_server (); */ + return 0; + } + + if (argc != 2) + { + fprintf (stderr, "usage: http-test uri\n"); + return 1; + } + argc--; + argv++; + +#ifdef HTTP_USE_GNUTLS + rc = gnutls_global_init (); + if (rc) + log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); + rc = gnutls_certificate_allocate_credentials (&certcred); + if (rc) + log_error ("gnutls_certificate_allocate_credentials failed: %s\n", + gnutls_strerror (rc)); +/* rc = gnutls_certificate_set_x509_trust_file */ +/* (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */ +/* if (rc) */ +/* log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */ +/* gnutls_strerror (rc)); */ + rc = gnutls_init (&tls_session, GNUTLS_CLIENT); + if (rc) + log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc)); + rc = gnutls_set_default_priority (tls_session); + if (rc) + log_error ("gnutls_set_default_priority failed: %s\n", + gnutls_strerror (rc)); + rc = gnutls_certificate_type_set_priority (tls_session, certprio); + if (rc) + log_error ("gnutls_certificate_type_set_priority failed: %s\n", + gnutls_strerror (rc)); + rc = gnutls_credentials_set (tls_session, GNUTLS_CRD_CERTIFICATE, certcred); + if (rc) + log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc)); +/* gnutls_global_set_log_function (my_gnutls_log); */ +/* gnutls_global_set_log_level (4); */ + + http_register_tls_callback (verify_callback); +#endif /*HTTP_USE_GNUTLS*/ + + rc = http_parse_uri (&uri, *argv); + if (rc) + { + log_error ("`%s': %s\n", *argv, gpg_strerror (rc)); + http_release_parsed_uri (uri); + return 1; + } + + printf ("Scheme: %s\n", uri->scheme); + printf ("Host : %s\n", uri->host); + printf ("Port : %u\n", uri->port); + printf ("Path : %s\n", uri->path); + for (r = uri->params; r; r = r->next) + { + printf ("Params: %s", r->name); + if (!r->no_value) + { + printf ("=%s", r->value); + if (strlen (r->value) != r->valuelen) + printf (" [real length=%d]", (int) r->valuelen); + } + putchar ('\n'); + } + for (r = uri->query; r; r = r->next) + { + printf ("Query : %s", r->name); + if (!r->no_value) + { + printf ("=%s", r->value); + if (strlen (r->value) != r->valuelen) + printf (" [real length=%d]", (int) r->valuelen); + } + putchar ('\n'); + } + http_release_parsed_uri (uri); + uri = NULL; + + rc = http_open_document (&hd, *argv, NULL, + HTTP_FLAG_NEED_HEADER, + NULL, tls_session); + if (rc) + { + log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc)); + return 1; + } + log_info ("open_http_document succeeded; status=%u\n", + http_get_status_code (hd)); + for (hdr = hd->headers; hdr; hdr = hdr->next) + printf ("HDR: %s: %s\n", hdr->name, hdr->value); + switch (http_get_status_code (hd)) + { + case 200: + while ((c = es_getc (http_get_read_ptr (hd))) != EOF) + putchar (c); + break; + case 301: + case 302: + printf ("Redirected to `%s'\n", http_get_header (hd, "Location")); + break; + } + http_close (hd, 0); + +#ifdef HTTP_USE_GNUTLS + gnutls_deinit (tls_session); + gnutls_certificate_free_credentials (certcred); + gnutls_global_deinit (); +#endif /*HTTP_USE_GNUTLS*/ + + return 0; +} +#endif /*TEST*/ + +/* +Local Variables: +compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls" +End: +*/ diff --git a/dirmngr/http.h b/dirmngr/http.h new file mode 100644 index 000000000..6e688b8d1 --- /dev/null +++ b/dirmngr/http.h @@ -0,0 +1,109 @@ +/* http.h - HTTP protocol handler + * Copyright (C) 1999, 2000, 2001, 2003, + * 2006 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 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ +#ifndef GNUPG_COMMON_HTTP_H +#define GNUPG_COMMON_HTTP_H + +#include +#include "estream.h" + +struct uri_tuple_s { + struct uri_tuple_s *next; + const char *name; /* A pointer into name. */ + char *value; /* A pointer to value (a Nul is always appended). */ + size_t valuelen; /* The real length of the value; we need it + because the value may contain embedded Nuls. */ + int no_value; /* True if no value has been given in the URL. */ +}; +typedef struct uri_tuple_s *uri_tuple_t; + +struct parsed_uri_s +{ + /* All these pointers point into BUFFER; most stuff is not escaped. */ + char *scheme; /* Pointer to the scheme string (lowercase). */ + int use_tls; /* Whether TLS should be used. */ + char *auth; /* username/password for basic auth */ + char *host; /* Host (converted to lowercase). */ + unsigned short port; /* Port (always set if the host is set). */ + char *path; /* Path. */ + uri_tuple_t params; /* ";xxxxx" */ + uri_tuple_t query; /* "?xxx=yyy" */ + char buffer[1]; /* Buffer which holds a (modified) copy of the URI. */ +}; +typedef struct parsed_uri_s *parsed_uri_t; + +typedef enum + { + HTTP_REQ_GET = 1, + HTTP_REQ_HEAD = 2, + HTTP_REQ_POST = 3 + } +http_req_t; + +/* We put the flag values into an enum, so that gdb can display them. */ +enum + { + HTTP_FLAG_TRY_PROXY = 1, + HTTP_FLAG_SHUTDOWN = 2, + HTTP_FLAG_TRY_SRV = 4, + HTTP_FLAG_LOG_RESP = 8, + HTTP_FLAG_NEED_HEADER = 16, + HTTP_FLAG_IGNORE_CL = 32 + }; + +struct http_context_s; +typedef struct http_context_s *http_t; + +void http_register_tls_callback (gpg_error_t (*cb) (http_t, void *, int)); + +gpg_error_t http_parse_uri (parsed_uri_t *ret_uri, const char *uri); + +void http_release_parsed_uri (parsed_uri_t uri); + +gpg_error_t http_open (http_t *r_hd, http_req_t reqtype, + const char *url, + const char *auth, + unsigned int flags, + const char *proxy, + void *tls_context); + +void http_start_data (http_t hd); + +gpg_error_t http_wait_response (http_t hd); + +void http_close (http_t hd, int keep_read_stream); + +gpg_error_t http_open_document (http_t *r_hd, + const char *document, + const char *auth, + unsigned int flags, + const char *proxy, + void *tls_context); + +estream_t http_get_read_ptr (http_t hd); +estream_t http_get_write_ptr (http_t hd); +unsigned int http_get_status_code (http_t hd); +const char *http_get_header (http_t hd, const char *name); + +char *http_escape_string (const char *string, const char *specials); + + +#endif /*GNUPG_COMMON_HTTP_H*/ diff --git a/dirmngr/ldap-url.c b/dirmngr/ldap-url.c new file mode 100644 index 000000000..eedcc6fd2 --- /dev/null +++ b/dirmngr/ldap-url.c @@ -0,0 +1,932 @@ +/* The following code comes from the OpenLDAP project. The references + to the COPYRIGHT file below refer to the corresponding file in the + OpenLDAP distribution, which is reproduced here in full: + +Copyright 1998-2004 The OpenLDAP Foundation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +A copy of this license is available in the file LICENSE in the +top-level directory of the distribution or, alternatively, at +. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Individual files and/or contributed packages may be copyright by +other parties and subject to additional restrictions. + +This work is derived from the University of Michigan LDAP v3.3 +distribution. Information concerning this software is available +at . + +This work also contains materials derived from public sources. + +Additional information about OpenLDAP can be obtained at +. + +--- + +Portions Copyright 1998-2004 Kurt D. Zeilenga. +Portions Copyright 1998-2004 Net Boolean Incorporated. +Portions Copyright 2001-2004 IBM Corporation. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +--- + +Portions Copyright 1999-2003 Howard Y.H. Chu. +Portions Copyright 1999-2003 Symas Corporation. +Portions Copyright 1998-2003 Hallvard B. Furuseth. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that this notice is preserved. +The names of the copyright holders may not be used to endorse or +promote products derived from this software without their specific +prior written permission. This software is provided ``as is'' +without express or implied warranty. + +--- + +Portions Copyright (c) 1992-1996 Regents of the University of Michigan. +All rights reserved. + +Redistribution and use in source and binary forms are permitted +provided that this notice is preserved and that due credit is given +to the University of Michigan at Ann Arbor. The name of the +University may not be used to endorse or promote products derived +from this software without specific prior written permission. This +software is provided ``as is'' without express or implied warranty. */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "ldap-url.h" +#define LDAP_P(protos) protos +#define LDAP_URL_URLCOLON "URL:" +#define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1) +#define LDAP_URL_PREFIX "ldap://" +#define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1) +#define LDAPS_URL_PREFIX "ldaps://" +#define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1) +#define LDAPI_URL_PREFIX "ldapi://" +#define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1) +#define LDAP_VFREE(v) { int _i; for (_i = 0; (v)[_i]; _i++) free((v)[_i]); } +#define LDAP_FREE free +#define LDAP_STRDUP strdup +#define LDAP_CALLOC calloc +#define LDAP_MALLOC malloc +#define LDAP_REALLOC realloc +#define ldap_utf8_strchr strchr +#define ldap_utf8_strtok(n,d,s) strtok (n,d) +#define Debug(a,b,c,d,e) +void ldap_pvt_hex_unescape( char *s ); + + + +/* $OpenLDAP: pkg/ldap/libraries/libldap/charray.c,v 1.9.2.2 2003/03/03 17:10:04 kurt Exp $ */ +/* + * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +/* charray.c - routines for dealing with char * arrays */ + +int +ldap_charray_add( + char ***a, + char *s +) +{ + int n; + + if ( *a == NULL ) { + *a = (char **) LDAP_MALLOC( 2 * sizeof(char *) ); + n = 0; + + if( *a == NULL ) { + return -1; + } + + } else { + char **new; + + for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { + ; /* NULL */ + } + + new = (char **) LDAP_REALLOC( (char *) *a, + (n + 2) * sizeof(char *) ); + + if( new == NULL ) { + /* caller is required to call ldap_charray_free(*a) */ + return -1; + } + + *a = new; + } + + (*a)[n] = LDAP_STRDUP(s); + + if( (*a)[n] == NULL ) { + return 1; + } + + (*a)[++n] = NULL; + + return 0; +} + +int +ldap_charray_merge( + char ***a, + char **s +) +{ + int i, n, nn; + char **aa; + + for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { + ; /* NULL */ + } + for ( nn = 0; s[nn] != NULL; nn++ ) { + ; /* NULL */ + } + + aa = (char **) LDAP_REALLOC( (char *) *a, (n + nn + 1) * sizeof(char *) ); + + if( aa == NULL ) { + return -1; + } + + *a = aa; + + for ( i = 0; i < nn; i++ ) { + (*a)[n + i] = LDAP_STRDUP(s[i]); + + if( (*a)[n + i] == NULL ) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( (*a)[n + i] ); + (*a)[n + i] = NULL; + } + return -1; + } + } + + (*a)[n + nn] = NULL; + return 0; +} + +void +ldap_charray_free( char **a ) +{ + char **p; + + if ( a == NULL ) { + return; + } + + for ( p = a; *p != NULL; p++ ) { + if ( *p != NULL ) { + LDAP_FREE( *p ); + } + } + + LDAP_FREE( (char *) a ); +} + +int +ldap_charray_inlist( + char **a, + char *s +) +{ + int i; + + if( a == NULL ) return 0; + + for ( i=0; a[i] != NULL; i++ ) { + if ( strcasecmp( s, a[i] ) == 0 ) { + return 1; + } + } + + return 0; +} + +char ** +ldap_charray_dup( char **a ) +{ + int i; + char **new; + + for ( i = 0; a[i] != NULL; i++ ) + ; /* NULL */ + + new = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); + + if( new == NULL ) { + return NULL; + } + + for ( i = 0; a[i] != NULL; i++ ) { + new[i] = LDAP_STRDUP( a[i] ); + + if( new[i] == NULL ) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( new[i] ); + } + LDAP_FREE( new ); + return NULL; + } + } + new[i] = NULL; + + return( new ); +} + +char ** +ldap_str2charray( const char *str_in, const char *brkstr ) +{ + char **res; + char *str, *s; + char *lasts; + int i; + + /* protect the input string from strtok */ + str = LDAP_STRDUP( str_in ); + if( str == NULL ) { + return NULL; + } + + i = 1; + for ( s = str; *s; s++ ) { + if ( ldap_utf8_strchr( brkstr, *s ) != NULL ) { + i++; + } + } + + res = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); + + if( res == NULL ) { + LDAP_FREE( str ); + return NULL; + } + + i = 0; + + for ( s = ldap_utf8_strtok( str, brkstr, &lasts ); + s != NULL; + s = ldap_utf8_strtok( NULL, brkstr, &lasts ) ) + { + res[i] = LDAP_STRDUP( s ); + + if(res[i] == NULL) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( res[i] ); + } + LDAP_FREE( res ); + LDAP_FREE( str ); + return NULL; + } + + i++; + } + + res[i] = NULL; + + LDAP_FREE( str ); + return( res ); +} + +char * ldap_charray2str( char **a, const char *sep ) +{ + char *s, **v, *p; + int len; + int slen; + + if( sep == NULL ) sep = " "; + + slen = strlen( sep ); + len = 0; + + for ( v = a; *v != NULL; v++ ) { + len += strlen( *v ) + slen; + } + + if ( len == 0 ) { + return NULL; + } + + /* trim extra sep len */ + len -= slen; + + s = LDAP_MALLOC ( len + 1 ); + + if ( s == NULL ) { + return NULL; + } + + p = s; + for ( v = a; *v != NULL; v++ ) { + if ( v != a ) { + strncpy( p, sep, slen ); + p += slen; + } + + len = strlen( *v ); + strncpy( p, *v, len ); + p += len; + } + + *p = '\0'; + return s; +} + + + +/* $OpenLDAP: pkg/ldap/libraries/libldap/url.c,v 1.64.2.5 2003/03/03 17:10:05 kurt Exp $ */ +/* + * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +/* Portions + * Copyright (c) 1996 Regents of the University of Michigan. + * All rights reserved. + * + * LIBLDAP url.c -- LDAP URL (RFC 2255) related routines + * + * LDAP URLs look like this: + * ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]] + * + * where: + * attributes is a comma separated list + * scope is one of these three strings: base one sub (default=base) + * filter is an string-represented filter as in RFC 2254 + * + * e.g., ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension + * + * We also tolerate URLs that look like: and + */ + +/* local functions */ +static const char* skip_url_prefix LDAP_P(( + const char *url, + int *enclosedp, + const char **scheme )); + +int +ldap_is_ldap_url( LDAP_CONST char *url ) +{ + int enclosed; + const char * scheme; + + if( url == NULL ) { + return 0; + } + + if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { + return 0; + } + + return 1; +} + + +static const char* +skip_url_prefix( + const char *url, + int *enclosedp, + const char **scheme ) +{ + /* + * return non-zero if this looks like a LDAP URL; zero if not + * if non-zero returned, *urlp will be moved past "ldap://" part of URL + */ + const char *p; + + if ( url == NULL ) { + return( NULL ); + } + + p = url; + + /* skip leading '<' (if any) */ + if ( *p == '<' ) { + *enclosedp = 1; + ++p; + } else { + *enclosedp = 0; + } + + /* skip leading "URL:" (if any) */ + if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { + p += LDAP_URL_URLCOLON_LEN; + } + + /* check for "ldap://" prefix */ + if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldap://" prefix and return success */ + p += LDAP_URL_PREFIX_LEN; + *scheme = "ldap"; + return( p ); + } + + /* check for "ldaps://" prefix */ + if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldaps://" prefix and return success */ + p += LDAPS_URL_PREFIX_LEN; + *scheme = "ldaps"; + return( p ); + } + + /* check for "ldapi://" prefix */ + if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldapi://" prefix and return success */ + p += LDAPI_URL_PREFIX_LEN; + *scheme = "ldapi"; + return( p ); + } + +#ifdef LDAP_CONNECTIONLESS + /* check for "cldap://" prefix */ + if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) { + /* skip over "cldap://" prefix and return success */ + p += LDAPC_URL_PREFIX_LEN; + *scheme = "cldap"; + return( p ); + } +#endif + + return( NULL ); +} + + +static int str2scope( const char *p ) +{ + if ( strcasecmp( p, "one" ) == 0 ) { + return LDAP_SCOPE_ONELEVEL; + + } else if ( strcasecmp( p, "onetree" ) == 0 ) { + return LDAP_SCOPE_ONELEVEL; + + } else if ( strcasecmp( p, "base" ) == 0 ) { + return LDAP_SCOPE_BASE; + + } else if ( strcasecmp( p, "sub" ) == 0 ) { + return LDAP_SCOPE_SUBTREE; + + } else if ( strcasecmp( p, "subtree" ) == 0 ) { + return LDAP_SCOPE_SUBTREE; + } + + return( -1 ); +} + + +int +ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) +{ +/* + * Pick apart the pieces of an LDAP URL. + */ + + LDAPURLDesc *ludp; + char *p, *q, *r; + int i, enclosed; + const char *scheme = NULL; + const char *url_tmp; + char *url; + + if( url_in == NULL || ludpp == NULL ) { + return LDAP_URL_ERR_PARAM; + } + +#ifndef LDAP_INT_IN_KERNEL + /* Global options may not be created yet + * We can't test if the global options are initialized + * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate + * the options and cause infinite recursion + */ +#ifdef NEW_LOGGING + LDAP_LOG ( OPERATION, ENTRY, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); +#else + Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); +#endif +#endif + + *ludpp = NULL; /* pessimistic */ + + url_tmp = skip_url_prefix( url_in, &enclosed, &scheme ); + + if ( url_tmp == NULL ) { + return LDAP_URL_ERR_BADSCHEME; + } + + assert( scheme ); + + /* make working copy of the remainder of the URL */ + url = LDAP_STRDUP( url_tmp ); + if ( url == NULL ) { + return LDAP_URL_ERR_MEM; + } + + if ( enclosed ) { + p = &url[strlen(url)-1]; + + if( *p != '>' ) { + LDAP_FREE( url ); + return LDAP_URL_ERR_BADENCLOSURE; + } + + *p = '\0'; + } + + /* allocate return struct */ + ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc )); + + if ( ludp == NULL ) { + LDAP_FREE( url ); + return LDAP_URL_ERR_MEM; + } + + ludp->lud_next = NULL; + ludp->lud_host = NULL; + ludp->lud_port = 0; + ludp->lud_dn = NULL; + ludp->lud_attrs = NULL; + ludp->lud_filter = NULL; + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + ludp->lud_filter = NULL; + ludp->lud_exts = NULL; + + ludp->lud_scheme = LDAP_STRDUP( scheme ); + + if ( ludp->lud_scheme == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + /* scan forward for '/' that marks end of hostport and begin. of dn */ + p = strchr( url, '/' ); + + if( p != NULL ) { + /* terminate hostport; point to start of dn */ + *p++ = '\0'; + } + + /* IPv6 syntax with [ip address]:port */ + if ( *url == '[' ) { + r = strchr( url, ']' ); + if ( r == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + *r++ = '\0'; + q = strchr( r, ':' ); + } else { + q = strchr( url, ':' ); + } + + if ( q != NULL ) { + *q++ = '\0'; + ldap_pvt_hex_unescape( q ); + + if( *q == '\0' ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + + ludp->lud_port = atoi( q ); + } + + ldap_pvt_hex_unescape( url ); + + /* If [ip address]:port syntax, url is [ip and we skip the [ */ + ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) ); + + if( ludp->lud_host == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + /* + * Kludge. ldap://111.222.333.444:389??cn=abc,o=company + * + * On early Novell releases, search references/referrals were returned + * in this format, i.e., the dn was kind of in the scope position, + * but the required slash is missing. The whole thing is illegal syntax, + * but we need to account for it. Fortunately it can't be confused with + * anything real. + */ + if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) { + q++; + /* ? immediately followed by question */ + if( *q == '?') { + q++; + if( *q != '\0' ) { + /* parse dn part */ + ldap_pvt_hex_unescape( q ); + ludp->lud_dn = LDAP_STRDUP( q ); + } else { + ludp->lud_dn = LDAP_STRDUP( "" ); + } + + if( ludp->lud_dn == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + } + } + + if( p == NULL ) { + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of dn */ + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate dn part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse dn part */ + ldap_pvt_hex_unescape( p ); + ludp->lud_dn = LDAP_STRDUP( p ); + } else { + ludp->lud_dn = LDAP_STRDUP( "" ); + } + + if( ludp->lud_dn == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + if( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of attributes */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate attributes part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse attributes */ + ldap_pvt_hex_unescape( p ); + ludp->lud_attrs = ldap_str2charray( p, "," ); + + if( ludp->lud_attrs == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADATTRS; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of scope */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate the scope part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse the scope */ + ldap_pvt_hex_unescape( p ); + ludp->lud_scope = str2scope( p ); + + if( ludp->lud_scope == -1 ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADSCOPE; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of filter */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate the filter part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse the filter */ + ldap_pvt_hex_unescape( p ); + + if( ! *p ) { + /* missing filter */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADFILTER; + } + + LDAP_FREE( ludp->lud_filter ); + ludp->lud_filter = LDAP_STRDUP( p ); + + if( ludp->lud_filter == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of extensions */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* extra '?' */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + + /* parse the extensions */ + ludp->lud_exts = ldap_str2charray( p, "," ); + + if( ludp->lud_exts == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADEXTS; + } + + for( i=0; ludp->lud_exts[i] != NULL; i++ ) { + ldap_pvt_hex_unescape( ludp->lud_exts[i] ); + + if( *ludp->lud_exts[i] == '!' ) { + /* count the number of critical extensions */ + ludp->lud_crit_exts++; + } + } + + if( i == 0 ) { + /* must have 1 or more */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADEXTS; + } + + /* no more */ + *ludpp = ludp; + LDAP_FREE( url ); + return LDAP_URL_SUCCESS; +} + +int +ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) +{ + int rc = ldap_url_parse_ext( url_in, ludpp ); + + if( rc != LDAP_URL_SUCCESS ) { + return rc; + } + + if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) { + (*ludpp)->lud_scope = LDAP_SCOPE_BASE; + } + + if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') { + LDAP_FREE( (*ludpp)->lud_host ); + (*ludpp)->lud_host = NULL; + } + + if ((*ludpp)->lud_port == 0) { + if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) { + (*ludpp)->lud_port = LDAP_PORT; +#ifdef LDAP_CONNECTIONLESS + } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) { + (*ludpp)->lud_port = LDAP_PORT; +#endif + } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) { + (*ludpp)->lud_port = LDAPS_PORT; + } + } + + return rc; +} + + +void +ldap_free_urldesc( LDAPURLDesc *ludp ) +{ + if ( ludp == NULL ) { + return; + } + + if ( ludp->lud_scheme != NULL ) { + LDAP_FREE( ludp->lud_scheme ); + } + + if ( ludp->lud_host != NULL ) { + LDAP_FREE( ludp->lud_host ); + } + + if ( ludp->lud_dn != NULL ) { + LDAP_FREE( ludp->lud_dn ); + } + + if ( ludp->lud_filter != NULL ) { + LDAP_FREE( ludp->lud_filter); + } + + if ( ludp->lud_attrs != NULL ) { + LDAP_VFREE( ludp->lud_attrs ); + } + + if ( ludp->lud_exts != NULL ) { + LDAP_VFREE( ludp->lud_exts ); + } + + LDAP_FREE( ludp ); +} + + +static int +ldap_int_unhex( int c ) +{ + return( c >= '0' && c <= '9' ? c - '0' + : c >= 'A' && c <= 'F' ? c - 'A' + 10 + : c - 'a' + 10 ); +} + +void +ldap_pvt_hex_unescape( char *s ) +{ + /* + * Remove URL hex escapes from s... done in place. The basic concept for + * this routine is borrowed from the WWW library HTUnEscape() routine. + */ + char *p; + + for ( p = s; *s != '\0'; ++s ) { + if ( *s == '%' ) { + if ( *++s == '\0' ) { + break; + } + *p = ldap_int_unhex( *s ) << 4; + if ( *++s == '\0' ) { + break; + } + *p++ += ldap_int_unhex( *s ); + } else { + *p++ = *s; + } + } + + *p = '\0'; +} + diff --git a/dirmngr/ldap-url.h b/dirmngr/ldap-url.h new file mode 100644 index 000000000..f3104d818 --- /dev/null +++ b/dirmngr/ldap-url.h @@ -0,0 +1,50 @@ +/* Copyright 2007 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. */ + +#ifndef LDAP_URL_H +#define LDAP_URL_H 1 + +#define LDAP_CONST const + +typedef struct ldap_url_desc +{ + struct ldap_url_desc *lud_next; + char *lud_scheme; + char *lud_host; + int lud_port; + char *lud_dn; + char **lud_attrs; + int lud_scope; + char *lud_filter; + char **lud_exts; + int lud_crit_exts; +} LDAPURLDesc; + +#define LDAP_URL_SUCCESS 0x00 +#define LDAP_URL_ERR_MEM 0x01 +#define LDAP_URL_ERR_PARAM 0x02 + +#define LDAP_URL_ERR_BADSCHEME 0x03 +#define LDAP_URL_ERR_BADENCLOSURE 0x04 +#define LDAP_URL_ERR_BADURL 0x05 +#define LDAP_URL_ERR_BADHOST 0x06 +#define LDAP_URL_ERR_BADATTRS 0x07 +#define LDAP_URL_ERR_BADSCOPE 0x08 +#define LDAP_URL_ERR_BADFILTER 0x09 +#define LDAP_URL_ERR_BADEXTS 0x0a + +#define LDAPS_PORT 636 + +int ldap_is_ldap_url (LDAP_CONST char *url); +int ldap_url_parse (LDAP_CONST char *url_in, LDAPURLDesc **ludpp); +void ldap_free_urldesc (LDAPURLDesc *ludp); + +#endif /* !LDAP_URL_H */ diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c new file mode 100644 index 000000000..fd3c3f510 --- /dev/null +++ b/dirmngr/ldap.c @@ -0,0 +1,1499 @@ +/* ldap.c - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010 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 +#include +#include + +#include "dirmngr.h" +#include "exechelp.h" +#include "crlfetch.h" +#include "ldapserver.h" +#include "misc.h" + +#ifdef HAVE_W32_SYSTEM +#define setenv(a,b,c) SetEnvironmentVariable ((a),(b)) +#else +#define pth_close(fd) close(fd) +#endif + + +/* In case sysconf does not return a value we need to have a limit. */ +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */ + +#define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "01234567890" \ + "$-_.+!*'()," +#define USERCERTIFICATE "userCertificate" +#define CACERTIFICATE "caCertificate" +#define X509CACERT "x509caCert" +#define USERSMIMECERTIFICATE "userSMIMECertificate" + + +/* Definition for the context of the cert fetch functions. */ +struct cert_fetch_context_s +{ + ksba_reader_t reader; /* The reader used (shallow copy). */ + unsigned char *tmpbuf; /* Helper buffer. */ + size_t tmpbufsize; /* Allocated size of tmpbuf. */ + int truncated; /* Flag to indicate a truncated output. */ +}; + + +/* To keep track of the LDAP wrapper state we use this structure. */ +struct wrapper_context_s +{ + struct wrapper_context_s *next; + + pid_t pid; /* The pid of the wrapper process. */ + int printable_pid; /* Helper to print diagnostics after the process has + been cleaned up. */ + int fd; /* Connected with stdout of the ldap wrapper. */ + gpg_error_t fd_error; /* Set to the gpg_error of the last read error + if any. */ + int log_fd; /* Connected with stderr of the ldap wrapper. */ + pth_event_t log_ev; + ctrl_t ctrl; /* Connection data. */ + int ready; /* Internally used to mark to be removed contexts. */ + ksba_reader_t reader; /* The ksba reader object or NULL. */ + char *line; /* Used to print the log lines (malloced). */ + size_t linesize;/* Allocated size of LINE. */ + size_t linelen; /* Use size of LINE. */ + time_t stamp; /* The last time we noticed ativity. */ +}; + + + + + +/* We keep a global list of spawed wrapper process. A separate thread + makes use of this list to log error messages and to watch out for + finished processes. */ +static struct wrapper_context_s *wrapper_list; + +/* We need to know whether we are shutting down the process. */ +static int shutting_down; + +/* Close the pth file descriptor FD and set it to -1. */ +#define SAFE_PTH_CLOSE(fd) \ + do { int _fd = fd; if (_fd != -1) { pth_close (_fd); fd = -1;} } while (0) + + +/* Prototypes. */ +static gpg_error_t read_buffer (ksba_reader_t reader, + unsigned char *buffer, size_t count); + + + + +/* Add HOST and PORT to our list of LDAP servers. Fixme: We should + better use an extra list of servers. */ +static void +add_server_to_servers (const char *host, int port) +{ + ldap_server_t server; + ldap_server_t last = NULL; + const char *s; + + if (!port) + port = 389; + + for (server=opt.ldapservers; server; server = server->next) + { + if (!strcmp (server->host, host) && server->port == port) + return; /* already in list... */ + last = server; + } + + /* We assume that the host names are all supplied by our + configuration files and thus are sane. To keep this assumption + we must reject all invalid host names. */ + for (s=host; *s; s++) + if (!strchr ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "01234567890.-", *s)) + { + log_error (_("invalid char 0x%02x in host name - not added\n"), *s); + return; + } + + log_info (_("adding `%s:%d' to the ldap server list\n"), host, port); + server = xtrycalloc (1, sizeof *s); + if (!server) + log_error (_("malloc failed: %s\n"), strerror (errno)); + else + { + server->host = xstrdup (host); + server->port = port; + if (last) + last->next = server; + else + opt.ldapservers = server; + } +} + + +/* Release the wrapper context and kill a running wrapper process. */ +static void +destroy_wrapper (struct wrapper_context_s *ctx) +{ + if (ctx->pid != (pid_t)(-1)) + { + gnupg_kill_process (ctx->pid); + gnupg_release_process (ctx->pid); + } + ksba_reader_release (ctx->reader); + SAFE_PTH_CLOSE (ctx->fd); + SAFE_PTH_CLOSE (ctx->log_fd); + if (ctx->log_ev) + pth_event_free (ctx->log_ev, PTH_FREE_THIS); + xfree (ctx->line); + xfree (ctx); +} + + +/* Print the content of LINE to thye log stream but make sure to only + print complete lines. Using NULL for LINE will flush any pending + output. LINE may be modified by this fucntion. */ +static void +print_log_line (struct wrapper_context_s *ctx, char *line) +{ + char *s; + size_t n; + + if (!line) + { + if (ctx->line && ctx->linelen) + { + + log_info ("%s\n", ctx->line); + ctx->linelen = 0; + } + return; + } + + while ((s = strchr (line, '\n'))) + { + *s = 0; + if (ctx->line && ctx->linelen) + { + log_info ("%s", ctx->line); + ctx->linelen = 0; + log_printf ("%s\n", line); + } + else + log_info ("%s\n", line); + line = s + 1; + } + n = strlen (line); + if (n) + { + if (ctx->linelen + n + 1 >= ctx->linesize) + { + char *tmp; + size_t newsize; + + newsize = ctx->linesize + ((n + 255) & ~255) + 1; + tmp = (ctx->line ? xtryrealloc (ctx->line, newsize) + : xtrymalloc (newsize)); + if (!tmp) + { + log_error (_("error printing log line: %s\n"), strerror (errno)); + return; + } + ctx->line = tmp; + ctx->linesize = newsize; + } + memcpy (ctx->line + ctx->linelen, line, n); + ctx->linelen += n; + ctx->line[ctx->linelen] = 0; + } +} + + +/* Read data from the log stream. Returns true if the log stream + indicated EOF or error. */ +static int +read_log_data (struct wrapper_context_s *ctx) +{ + int n; + char line[256]; + + /* We must use the pth_read function for pipes, always. */ + do + n = pth_read (ctx->log_fd, line, sizeof line - 1); + while (n < 0 && errno == EINTR); + + if (n <= 0) /* EOF or error. */ + { + if (n < 0) + log_error (_("error reading log from ldap wrapper %d: %s\n"), + ctx->pid, strerror (errno)); + print_log_line (ctx, NULL); + SAFE_PTH_CLOSE (ctx->log_fd); + pth_event_free (ctx->log_ev, PTH_FREE_THIS); + ctx->log_ev = NULL; + return 1; + } + + line[n] = 0; + print_log_line (ctx, line); + if (ctx->stamp != (time_t)(-1)) + ctx->stamp = time (NULL); + return 0; +} + + +/* This function is run by a separate thread to maintain the list of + wrappers and to log error messages from these wrappers. */ +void * +ldap_wrapper_thread (void *dummy) +{ + int nfds; + struct wrapper_context_s *ctx; + struct wrapper_context_s *ctx_prev; + time_t current_time; + + (void)dummy; + + for (;;) + { + pth_event_t timeout_ev; + int any_action = 0; + + timeout_ev = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); + if (! timeout_ev) + { + log_error (_("pth_event failed: %s\n"), strerror (errno)); + pth_sleep (10); + continue; + } + + for (ctx = wrapper_list; ctx; ctx = ctx->next) + { + if (ctx->log_fd != -1) + { + pth_event_isolate (ctx->log_ev); + pth_event_concat (timeout_ev, ctx->log_ev, NULL); + } + } + + /* Note that the read FDs are actually handles. Thus, we can + not use pth_select, but have to use pth_wait. */ + nfds = pth_wait (timeout_ev); + if (nfds < 0) + { + pth_event_free (timeout_ev, PTH_FREE_THIS); + log_error (_("pth_wait failed: %s\n"), strerror (errno)); + pth_sleep (10); + continue; + } + if (pth_event_status (timeout_ev) == PTH_STATUS_OCCURRED) + nfds--; + pth_event_free (timeout_ev, PTH_FREE_THIS); + + current_time = time (NULL); + if (current_time > INACTIVITY_TIMEOUT) + current_time -= INACTIVITY_TIMEOUT; + + /* Note that there is no need to lock the list because we always + add entries at the head (with a pending event status) and + thus traversing the list will even work if we have a context + switch in waitpid (which should anyway only happen with Pth's + hard system call mapping). */ + for (ctx = wrapper_list; ctx; ctx = ctx->next) + { + /* Check whether there is any logging to be done. */ + if (nfds && ctx->log_fd != -1 + && pth_event_status (ctx->log_ev) == PTH_STATUS_OCCURRED) + { + if (read_log_data (ctx)) + any_action = 1; + } + + /* Check whether the process is still running. */ + if (ctx->pid != (pid_t)(-1)) + { + gpg_error_t err; + int status; + + err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, + &status); + if (!err) + { + log_info (_("ldap wrapper %d ready"), (int)ctx->pid); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) == GPG_ERR_GENERAL) + { + if (status == 10) + log_info (_("ldap wrapper %d ready: timeout\n"), + (int)ctx->pid); + else + log_info (_("ldap wrapper %d ready: exitcode=%d\n"), + (int)ctx->pid, status); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) + { + log_error (_("waiting for ldap wrapper %d failed: %s\n"), + (int)ctx->pid, gpg_strerror (err)); + any_action = 1; + } + } + + /* Check whether we should terminate the process. */ + if (ctx->pid != (pid_t)(-1) + && ctx->stamp != (time_t)(-1) && ctx->stamp < current_time) + { + gnupg_kill_process (ctx->pid); + ctx->stamp = (time_t)(-1); + log_info (_("ldap wrapper %d stalled - killing\n"), + (int)ctx->pid); + /* We need to close the log fd because the cleanup loop + waits for it. */ + SAFE_PTH_CLOSE (ctx->log_fd); + any_action = 1; + } + } + + /* If something has been printed to the log file or we got an + EOF from a wrapper, we now print the list of active + wrappers. */ + if (any_action && DBG_LOOKUP) + { + log_info ("ldap worker stati:\n"); + for (ctx = wrapper_list; ctx; ctx = ctx->next) + log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, + (unsigned long)ctx->stamp, ctx->ready); + } + + + /* Use a separate loop to check whether ready marked wrappers + may be removed. We may only do so if the ksba reader object + is not anymore in use or we are in shutdown state. */ + again: + for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next) + if (ctx->ready + && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down)) + { + if (ctx_prev) + ctx_prev->next = ctx->next; + else + wrapper_list = ctx->next; + destroy_wrapper (ctx); + /* We need to restart because destroy_wrapper might have + done a context switch. */ + goto again; + } + } + /*NOTREACHED*/ + return NULL; /* Make the compiler happy. */ +} + + + +/* Wait until all ldap wrappers have terminated. We assume that the + kill has already been sent to all of them. */ +void +ldap_wrapper_wait_connections () +{ + shutting_down = 1; + while (wrapper_list) + pth_yield (NULL); +} + + +/* This function is to be used to release a context associated with the + given reader object. */ +void +ldap_wrapper_release_context (ksba_reader_t reader) +{ + struct wrapper_context_s *ctx; + + if (!reader ) + return; + + for (ctx=wrapper_list; ctx; ctx=ctx->next) + if (ctx->reader == reader) + { + if (DBG_LOOKUP) + log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); + + ctx->reader = NULL; + SAFE_PTH_CLOSE (ctx->fd); + if (ctx->ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + } + if (ctx->fd_error) + log_info (_("reading from ldap wrapper %d failed: %s\n"), + ctx->printable_pid, gpg_strerror (ctx->fd_error)); + break; + } +} + +/* Cleanup all resources held by the connection associated with + CTRL. This is used after a cancel to kill running wrappers. */ +void +ldap_wrapper_connection_cleanup (ctrl_t ctrl) +{ + struct wrapper_context_s *ctx; + + for (ctx=wrapper_list; ctx; ctx=ctx->next) + if (ctx->ctrl && ctx->ctrl == ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + if (ctx->pid != (pid_t)(-1)) + gnupg_kill_process (ctx->pid); + if (ctx->fd_error) + log_info (_("reading from ldap wrapper %d failed: %s\n"), + ctx->printable_pid, gpg_strerror (ctx->fd_error)); + } +} + +/* This is the callback used by the ldap wrapper to feed the ksba + reader with the wrappers stdout. See the description of + ksba_reader_set_cb for details. */ +static int +reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct wrapper_context_s *ctx = cb_value; + size_t nleft = count; + + /* FIXME: We might want to add some internal buffering because the + ksba code does not do any buffering for itself (because a ksba + reader may be detached from another stream to read other data and + the it would be cumbersome to get back already buffered + stuff). */ + + if (!buffer && !count && !nread) + return -1; /* Rewind is not supported. */ + + /* If we ever encountered a read error don't allow to continue and + possible overwrite the last error cause. Bail out also if the + file descriptor has been closed. */ + if (ctx->fd_error || ctx->fd == -1) + { + *nread = 0; + return -1; + } + + while (nleft > 0) + { + int n; + pth_event_t evt; + gpg_error_t err; + + evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); + n = pth_read_ev (ctx->fd, buffer, nleft, evt); + if (n < 0 && evt && pth_event_occurred (evt)) + { + n = 0; + err = dirmngr_tick (ctx->ctrl); + if (err) + { + ctx->fd_error = err; + SAFE_PTH_CLOSE (ctx->fd); + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + return -1; + } + + } + else if (n < 0) + { + ctx->fd_error = gpg_error_from_errno (errno); + SAFE_PTH_CLOSE (ctx->fd); + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + return -1; + } + else if (!n) + { + if (nleft == count) + { + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + return -1; /* EOF. */ + } + break; + } + nleft -= n; + buffer += n; + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + if (n > 0 && ctx->stamp != (time_t)(-1)) + ctx->stamp = time (NULL); + } + *nread = count - nleft; + + return 0; + +} + +/* Fork and exec the LDAP wrapper and returns a new libksba reader + object at READER. ARGV is a NULL terminated list of arguments for + the wrapper. The function returns 0 on success or an error code. + + We can't use LDAP directly for these reasons: + + 1. On some systems the LDAP library uses (indirectly) pthreads and + that is not compatible with PTh. + + 2. It is huge library in particular if TLS comes into play. So + problems with unfreed memory might turn up and we don't want + this in a long running daemon. + + 3. There is no easy way for timeouts. In particular the timeout + value does not work for DNS lookups (well, this is usual) and it + seems not to work while loading a large attribute like a + CRL. Having a separate process allows us to either tell the + process to commit suicide or have our own housekepping function + kill it after some time. The latter also allows proper + cancellation of a query at any point of time. + + 4. Given that we are going out to the network and usually get back + a long response, the fork/exec overhead is acceptable. + + Special hack to avoid passing a password through the command line + which is globally visible: If the first element of ARGV is "--pass" + it will be removed and instead the environment variable + DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern + OSes the environment is not visible to other users. For those old + systems where it can't be avoided, we don't want to go into the + hassle of passing the password via stdin; it's just too complicated + and an LDAP password used for public directory lookups should not + be that confidential. */ +static gpg_error_t +ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) +{ + gpg_error_t err; + pid_t pid; + struct wrapper_context_s *ctx; + int i; + int j; + const char **arg_list; + const char *pgmname; + int outpipe[2], errpipe[2]; + + /* It would be too simple to connect stderr just to our logging + stream. The problem is that if we are running multi-threaded + everything gets intermixed. Clearly we don't want this. So the + only viable solutions are either to have another thread + responsible for logging the messages or to add an option to the + wrapper module to do the logging on its own. Given that we anyway + need a way to rip the child process and this is best done using a + general ripping thread, that thread can do the logging too. */ + + *reader = NULL; + + /* Files: We need to prepare stdin and stdout. We get stderr from + the function. */ + if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program) + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP); + else + pgmname = opt.ldap_wrapper_program; + + /* Create command line argument array. */ + for (i = 0; argv[i]; i++) + ; + arg_list = xtrycalloc (i + 2, sizeof *arg_list); + if (!arg_list) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + return err; + } + for (i = j = 0; argv[i]; i++, j++) + if (!i && argv[i + 1] && !strcmp (*argv, "--pass")) + { + arg_list[j] = "--env-pass"; + setenv ("DIRMNGR_LDAP_PASS", argv[1], 1); + i++; + } + else + arg_list[j] = (char*) argv[i]; + + ctx = xtrycalloc (1, sizeof *ctx); + if (!ctx) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + xfree (arg_list); + return err; + } + + err = gnupg_create_inbound_pipe (outpipe); + if (!err) + { + err = gnupg_create_inbound_pipe (errpipe); + if (err) + { + close (outpipe[0]); + close (outpipe[1]); + } + } + if (err) + { + log_error (_("error creating pipe: %s\n"), gpg_strerror (err)); + xfree (arg_list); + xfree (ctx); + return err; + } + + err = gnupg_spawn_process_fd (pgmname, arg_list, + -1, outpipe[1], errpipe[1], &pid); + xfree (arg_list); + close (outpipe[1]); + close (errpipe[1]); + if (err) + { + close (outpipe[0]); + close (errpipe[0]); + xfree (ctx); + return err; + } + + ctx->pid = pid; + ctx->printable_pid = (int) pid; + ctx->fd = outpipe[0]; + ctx->log_fd = errpipe[0]; + ctx->log_ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, ctx->log_fd); + if (! ctx->log_ev) + { + xfree (ctx); + return gpg_error_from_syserror (); + } + ctx->ctrl = ctrl; + ctrl->refcount++; + ctx->stamp = time (NULL); + + err = ksba_reader_new (reader); + if (!err) + err = ksba_reader_set_cb (*reader, reader_callback, ctx); + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + destroy_wrapper (ctx); + ksba_reader_release (*reader); + *reader = NULL; + return err; + } + + /* Hook the context into our list of running wrappers. */ + ctx->reader = *reader; + ctx->next = wrapper_list; + wrapper_list = ctx; + if (opt.verbose) + log_info ("ldap wrapper %d started (reader %p)\n", + (int)ctx->pid, ctx->reader); + + /* Need to wait for the first byte so we are able to detect an empty + output and not let the consumer see an EOF without further error + indications. The CRL loading logic assumes that after return + from this function, a failed search (e.g. host not found ) is + indicated right away. */ + { + unsigned char c; + + err = read_buffer (*reader, &c, 1); + if (err) + { + ldap_wrapper_release_context (*reader); + ksba_reader_release (*reader); + *reader = NULL; + if (gpg_err_code (err) == GPG_ERR_EOF) + return gpg_error (GPG_ERR_NO_DATA); + else + return err; + } + ksba_reader_unread (*reader, &c, 1); + } + + return 0; +} + + + +/* Perform an LDAP query. Returns an gpg error code or 0 on success. + The function returns a new reader object at READER. */ +static gpg_error_t +run_ldap_wrapper (ctrl_t ctrl, + int ignore_timeout, + int multi_mode, + const char *proxy, + const char *host, int port, + const char *user, const char *pass, + const char *dn, const char *filter, const char *attr, + const char *url, + ksba_reader_t *reader) +{ + const char *argv[40]; + int argc; + char portbuf[30], timeoutbuf[30]; + + + *reader = NULL; + + argc = 0; + if (pass) /* Note, that the password most be the first item. */ + { + argv[argc++] = "--pass"; + argv[argc++] = pass; + } + if (opt.verbose) + argv[argc++] = "-vv"; + argv[argc++] = "--log-with-pid"; + if (multi_mode) + argv[argc++] = "--multi"; + if (opt.ldaptimeout) + { + sprintf (timeoutbuf, "%u", opt.ldaptimeout); + argv[argc++] = "--timeout"; + argv[argc++] = timeoutbuf; + if (ignore_timeout) + argv[argc++] = "--only-search-timeout"; + } + if (proxy) + { + argv[argc++] = "--proxy"; + argv[argc++] = proxy; + } + if (host) + { + argv[argc++] = "--host"; + argv[argc++] = host; + } + if (port) + { + sprintf (portbuf, "%d", port); + argv[argc++] = "--port"; + argv[argc++] = portbuf; + } + if (user) + { + argv[argc++] = "--user"; + argv[argc++] = user; + } + if (dn) + { + argv[argc++] = "--dn"; + argv[argc++] = dn; + } + if (filter) + { + argv[argc++] = "--filter"; + argv[argc++] = filter; + } + if (attr) + { + argv[argc++] = "--attr"; + argv[argc++] = attr; + } + argv[argc++] = url? url : "ldap://"; + argv[argc] = NULL; + + return ldap_wrapper (ctrl, reader, argv); +} + + + + +/* Perform a LDAP query using a given URL. On success a new ksba + reader is returned. If HOST or PORT are not 0, they are used to + override the values from the URL. */ +gpg_error_t +url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port, + ksba_reader_t *reader) +{ + gpg_error_t err; + + err = run_ldap_wrapper (ctrl, + 1, /* Ignore explicit timeout because CRLs + might be very large. */ + 0, + opt.ldap_proxy, + host, port, + NULL, NULL, + NULL, NULL, NULL, url, + reader); + + /* FIXME: This option might be used for DoS attacks. Because it + will enlarge the list of servers to consult without a limit and + all LDAP queries w/o a host are will then try each host in + turn. */ + if (!err && opt.add_new_ldapservers && !opt.ldap_proxy) + { + if (host) + add_server_to_servers (host, port); + else if (url) + { + char *tmp = host_and_port_from_url (url, &port); + if (tmp) + { + add_server_to_servers (tmp, port); + xfree (tmp); + } + } + } + + /* If the lookup failed and we are not only using the proxy, we try + again using our default list of servers. */ + if (err && !(opt.ldap_proxy && opt.only_ldap_proxy)) + { + struct ldapserver_iter iter; + + if (DBG_LOOKUP) + log_debug ("no hostname in URL or query failed; " + "trying all default hostnames\n"); + + for (ldapserver_iter_begin (&iter, ctrl); + err && ! ldapserver_iter_end_p (&iter); + ldapserver_iter_next (&iter)) + { + ldap_server_t server = iter.server; + + err = run_ldap_wrapper (ctrl, + 0, + 0, + NULL, + server->host, server->port, + NULL, NULL, + NULL, NULL, NULL, url, + reader); + if (!err) + break; + } + } + + return err; +} + + + +/* Perform an LDAP query on all configured servers. On error the + error code of the last try is returned. */ +gpg_error_t +attr_fetch_ldap (ctrl_t ctrl, + const char *dn, const char *attr, ksba_reader_t *reader) +{ + gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION); + struct ldapserver_iter iter; + + *reader = NULL; + + /* FIXME; we might want to look at the Base SN to try matching + servers first. */ + for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter); + ldapserver_iter_next (&iter)) + { + ldap_server_t server = iter.server; + + err = run_ldap_wrapper (ctrl, + 0, + 0, + opt.ldap_proxy, + server->host, server->port, + server->user, server->pass, + dn, "objectClass=*", attr, NULL, + reader); + if (!err) + break; /* Probably found a result. Ready. */ + } + return err; +} + + +/* Parse PATTERN and return a new strlist to be used for the actual + LDAP query. Bit 0 of the flags field is set if that pattern is + actually a base specification. Caller must release the returned + strlist. NULL is returned on error. + + * Possible patterns: + * + * KeyID + * Fingerprint + * OpenPGP userid + * x Email address Indicated by a left angle bracket. + * Exact word match in user id or subj. name + * x Subj. DN indicated bu a leading slash + * Issuer DN + * Serial number + subj. DN + * x Substring match indicated by a leading '*; is also the default. + */ + +strlist_t +parse_one_pattern (const char *pattern) +{ + strlist_t result = NULL; + char *p; + + switch (*pattern) + { + case '<': /* Email. */ + { + pattern++; + result = xmalloc (sizeof *result + 5 + strlen (pattern)); + result->next = NULL; + result->flags = 0; + p = stpcpy (stpcpy (result->d, "mail="), pattern); + if (p[-1] == '>') + *--p = 0; + if (!*result->d) /* Error. */ + { + xfree (result); + result = NULL; + } + break; + } + case '/': /* Subject DN. */ + pattern++; + if (*pattern) + { + result = xmalloc (sizeof *result + strlen (pattern)); + result->next = NULL; + result->flags = 1; /* Base spec. */ + strcpy (result->d, pattern); + } + break; + case '#': /* Issuer DN. */ + pattern++; + if (*pattern == '/') /* Just issuer DN. */ + { + pattern++; + } + else /* Serial number + issuer DN */ + { + } + break; + case '*': + pattern++; + default: /* Take as substring match. */ + { + const char format[] = "(|(sn=*%s*)(|(cn=*%s*)(mail=*%s*)))"; + + if (*pattern) + { + result = xmalloc (sizeof *result + + strlen (format) + 3 * strlen (pattern)); + result->next = NULL; + result->flags = 0; + sprintf (result->d, format, pattern, pattern, pattern); + } + } + break; + } + + return result; +} + +/* Take the string STRING and escape it accoring to the URL rules. + Retun a newly allocated string. */ +static char * +escape4url (const char *string) +{ + const char *s; + char *buf, *p; + size_t n; + + if (!string) + string = ""; + + for (s=string,n=0; *s; s++) + if (strchr (UNENCODED_URL_CHARS, *s)) + n++; + else + n += 3; + + buf = malloc (n+1); + if (!buf) + return NULL; + + for (s=string,p=buf; *s; s++) + if (strchr (UNENCODED_URL_CHARS, *s)) + *p++ = *s; + else + { + sprintf (p, "%%%02X", *(const unsigned char *)s); + p += 3; + } + *p = 0; + + return buf; +} + + + +/* Create a LDAP URL from DN and FILTER and return it in URL. We don't + need the host and port because this will be specified using the + override options. */ +static gpg_error_t +make_url (char **url, const char *dn, const char *filter) +{ + gpg_error_t err; + char *u_dn, *u_filter; + char const attrs[] = (USERCERTIFICATE "," +/* USERSMIMECERTIFICATE "," */ + CACERTIFICATE "," + X509CACERT ); + + *url = NULL; + + u_dn = escape4url (dn); + if (!u_dn) + return gpg_error_from_errno (errno); + + u_filter = escape4url (filter); + if (!u_filter) + { + err = gpg_error_from_errno (errno); + xfree (u_dn); + return err; + } + *url = malloc ( 8 + strlen (u_dn) + + 1 + strlen (attrs) + + 5 + strlen (u_filter) + 1 ); + if (!*url) + { + err = gpg_error_from_errno (errno); + xfree (u_dn); + xfree (u_filter); + return err; + } + + stpcpy (stpcpy (stpcpy (stpcpy (stpcpy (stpcpy (*url, "ldap:///"), + u_dn), + "?"), + attrs), + "?sub?"), + u_filter); + xfree (u_dn); + xfree (u_filter); + return 0; +} + + +/* Prepare an LDAP query to return the attribute ATTR for the DN. All + configured default servers are queried until one responds. This + function returns an error code or 0 and a CONTEXT on success. */ +gpg_error_t +start_default_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context, + const char *dn, const char *attr) +{ + gpg_error_t err; + struct ldapserver_iter iter; + + *context = xtrycalloc (1, sizeof **context); + if (!*context) + return gpg_error_from_errno (errno); + + /* FIXME; we might want to look at the Base SN to try matching + servers first. */ + err = gpg_error (GPG_ERR_CONFIGURATION); + + for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter); + ldapserver_iter_next (&iter)) + { + ldap_server_t server = iter.server; + + err = run_ldap_wrapper (ctrl, + 0, + 1, + opt.ldap_proxy, + server->host, server->port, + server->user, server->pass, + dn, "objectClass=*", attr, NULL, + &(*context)->reader); + if (!err) + break; /* Probably found a result. */ + } + + if (err) + { + xfree (*context); + *context = NULL; + } + return err; +} + + +/* Prepare an LDAP query to return certificates maching PATTERNS using + the SERVER. This function returns an error code or 0 and a CONTEXT + on success. */ +gpg_error_t +start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context, + strlist_t patterns, const ldap_server_t server) +{ + gpg_error_t err; + const char *host; + int port; + const char *user; + const char *pass; + const char *base; + const char *argv[50]; + int argc; + char portbuf[30], timeoutbuf[30]; + + + *context = NULL; + if (server) + { + host = server->host; + port = server->port; + user = server->user; + pass = server->pass; + base = server->base; + } + else /* Use a default server. */ + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + if (!base) + base = ""; + + argc = 0; + if (pass) /* Note: Must be the first item. */ + { + argv[argc++] = "--pass"; + argv[argc++] = pass; + } + if (opt.verbose) + argv[argc++] = "-vv"; + argv[argc++] = "--log-with-pid"; + argv[argc++] = "--multi"; + if (opt.ldaptimeout) + { + sprintf (timeoutbuf, "%u", opt.ldaptimeout); + argv[argc++] = "--timeout"; + argv[argc++] = timeoutbuf; + } + if (opt.ldap_proxy) + { + argv[argc++] = "--proxy"; + argv[argc++] = opt.ldap_proxy; + } + if (host) + { + argv[argc++] = "--host"; + argv[argc++] = host; + } + if (port) + { + sprintf (portbuf, "%d", port); + argv[argc++] = "--port"; + argv[argc++] = portbuf; + } + if (user) + { + argv[argc++] = "--user"; + argv[argc++] = user; + } + + + for (; patterns; patterns = patterns->next) + { + strlist_t sl; + char *url; + + if (argc >= sizeof argv -1) + { + /* Too many patterns. It does not make sense to allow an + arbitrary number of patters because the length of the + command line is limited anyway. */ + /* fixme: cleanup. */ + return gpg_error (GPG_ERR_RESOURCE_LIMIT); + } + sl = parse_one_pattern (patterns->d); + if (!sl) + { + log_error (_("start_cert_fetch: invalid pattern `%s'\n"), + patterns->d); + /* fixme: cleanup argv. */ + return gpg_error (GPG_ERR_INV_USER_ID); + } + if ((sl->flags & 1)) + err = make_url (&url, sl->d, "objectClass=*"); + else + err = make_url (&url, base, sl->d); + free_strlist (sl); + if (err) + { + /* fixme: cleanup argv. */ + return err; + } + argv[argc++] = url; + } + argv[argc] = NULL; + + *context = xtrycalloc (1, sizeof **context); + if (!*context) + return gpg_error_from_errno (errno); + + err = ldap_wrapper (ctrl, &(*context)->reader, argv); + + if (err) + { + xfree (*context); + *context = NULL; + } + + return err; +} + + +/* Read a fixed amount of data from READER into BUFFER. */ +static gpg_error_t +read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) +{ + gpg_error_t err; + size_t nread; + + while (count) + { + err = ksba_reader_read (reader, buffer, count, &nread); + if (err) + return err; + buffer += nread; + count -= nread; + } + return 0; +} + + +/* Fetch the next certificate. Return 0 on success, GPG_ERR_EOF if no + (more) certificates are available or any other error + code. GPG_ERR_TRUNCATED may be returned to indicate that the result + has been truncated. */ +gpg_error_t +fetch_next_cert_ldap (cert_fetch_context_t context, + unsigned char **value, size_t *valuelen) +{ + gpg_error_t err; + unsigned char hdr[5]; + char *p, *pend; + int n; + int okay = 0; + int is_cms = 0; + + *value = NULL; + *valuelen = 0; + + err = 0; + while (!err) + { + err = read_buffer (context->reader, hdr, 5); + if (err) + break; + n = (hdr[1] << 24)|(hdr[2]<<16)|(hdr[3]<<8)|hdr[4]; + if (*hdr == 'V' && okay) + { +#if 0 /* That code is not yet ready. */ + + if (is_cms) + { + /* The certificate needs to be parsed from CMS data. */ + ksba_cms_t cms; + ksba_stop_reason_t stopreason; + int i; + + err = ksba_cms_new (&cms); + if (err) + goto leave; + err = ksba_cms_set_reader_writer (cms, context->reader, NULL); + if (err) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + gpg_strerror (err)); + goto leave; + } + + do + { + err = ksba_cms_parse (cms, &stopreason); + if (err) + { + log_error ("ksba_cms_parse failed: %s\n", + gpg_strerror (err)); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + log_error ("userSMIMECertificate is not " + "a certs-only message\n"); + } + while (stopreason != KSBA_SR_READY); + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + check_and_store (ctrl, stats, cert, 0); + ksba_cert_release (cert); + cert = NULL; + } + if (!i) + log_error ("no certificate found\n"); + else + any = 1; + } + else +#endif + { + *value = xtrymalloc (n); + if (!*value) + return gpg_error_from_errno (errno); + *valuelen = n; + err = read_buffer (context->reader, *value, n); + break; /* Ready or error. */ + } + } + else if (!n && *hdr == 'A') + okay = 0; + else if (n) + { + if (n > context->tmpbufsize) + { + xfree (context->tmpbuf); + context->tmpbufsize = 0; + context->tmpbuf = xtrymalloc (n+1); + if (!context->tmpbuf) + return gpg_error_from_errno (errno); + context->tmpbufsize = n; + } + err = read_buffer (context->reader, context->tmpbuf, n); + if (err) + break; + if (*hdr == 'A') + { + p = context->tmpbuf; + p[n] = 0; /*(we allocated one extra byte for this.)*/ + is_cms = 0; + if ( (pend = strchr (p, ';')) ) + *pend = 0; /* Strip off the extension. */ + if (!ascii_strcasecmp (p, USERCERTIFICATE)) + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute `%s'\n", + USERCERTIFICATE); + okay = 1; + } + else if (!ascii_strcasecmp (p, CACERTIFICATE)) + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute `%s'\n", + CACERTIFICATE); + okay = 1; + } + else if (!ascii_strcasecmp (p, X509CACERT)) + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute `%s'\n", + CACERTIFICATE); + okay = 1; + } +/* else if (!ascii_strcasecmp (p, USERSMIMECERTIFICATE)) */ +/* { */ +/* if (DBG_LOOKUP) */ +/* log_debug ("fetch_next_cert_ldap: got attribute `%s'\n", */ +/* USERSMIMECERTIFICATE); */ +/* okay = 1; */ +/* is_cms = 1; */ +/* } */ + else + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute `%s'" + " - ignored\n", p); + okay = 0; + } + } + else if (*hdr == 'E') + { + p = context->tmpbuf; + p[n] = 0; /*(we allocated one extra byte for this.)*/ + if (!strcmp (p, "truncated")) + { + context->truncated = 1; + log_info (_("ldap_search hit the size limit of" + " the server\n")); + } + } + } + } + + if (err) + { + xfree (*value); + *value = NULL; + *valuelen = 0; + if (gpg_err_code (err) == GPG_ERR_EOF && context->truncated) + { + context->truncated = 0; /* So that the next call would return EOF. */ + err = gpg_error (GPG_ERR_TRUNCATED); + } + } + + return err; +} + + +void +end_cert_fetch_ldap (cert_fetch_context_t context) +{ + if (context) + { + ksba_reader_t reader = context->reader; + + xfree (context->tmpbuf); + xfree (context); + ldap_wrapper_release_context (reader); + ksba_reader_release (reader); + } +} diff --git a/dirmngr/ldapserver.c b/dirmngr/ldapserver.c new file mode 100644 index 000000000..da702ec52 --- /dev/null +++ b/dirmngr/ldapserver.c @@ -0,0 +1,133 @@ +/* dirmngr.c - LDAP access + Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "dirmngr.h" +#include "ldapserver.h" + + +/* Release the list of SERVERS. As usual it is okay to call this + function with SERVERS passed as NULL. */ +void +ldapserver_list_free (ldap_server_t servers) +{ + while (servers) + { + ldap_server_t tmp = servers->next; + xfree (servers->host); + xfree (servers->user); + if (servers->pass) + memset (servers->pass, 0, strlen (servers->pass)); + xfree (servers->pass); + xfree (servers->base); + xfree (servers); + servers = tmp; + } +} + + +/* Parse a single LDAP server configuration line. Returns the server + or NULL in case of errors. The configuration lineis assumed to be + colon seprated with these fields: + + 1. field: Hostname + 2. field: Portnumber + 3. field: Username + 4. field: Password + 5. field: Base DN + + FILENAME and LINENO are used for diagnostic purposes only. +*/ +ldap_server_t +ldapserver_parse_one (char *line, + const char *filename, unsigned int lineno) +{ + char *p; + char *endp; + ldap_server_t server; + int fieldno; + int fail = 0; + + /* Parse the colon separated fields. */ + server = xcalloc (1, sizeof *server); + for (fieldno = 1, p = line; p; p = endp, fieldno++ ) + { + endp = strchr (p, ':'); + if (endp) + *endp++ = '\0'; + trim_spaces (p); + switch (fieldno) + { + case 1: + if (*p) + server->host = xstrdup (p); + else + { + log_error (_("%s:%u: no hostname given\n"), + filename, lineno); + fail = 1; + } + break; + + case 2: + if (*p) + server->port = atoi (p); + break; + + case 3: + if (*p) + server->user = xstrdup (p); + break; + + case 4: + if (*p && !server->user) + { + log_error (_("%s:%u: password given without user\n"), + filename, lineno); + fail = 1; + } + else if (*p) + server->pass = xstrdup (p); + break; + + case 5: + if (*p) + server->base = xstrdup (p); + break; + + default: + /* (We silently ignore extra fields.) */ + break; + } + } + + if (fail) + { + log_info (_("%s:%u: skipping this line\n"), filename, lineno); + ldapserver_list_free (server); + } + + return server; +} + + diff --git a/dirmngr/ldapserver.h b/dirmngr/ldapserver.h new file mode 100644 index 000000000..eb1ab226d --- /dev/null +++ b/dirmngr/ldapserver.h @@ -0,0 +1,90 @@ +/* ldapserver.h + Copyright (C) 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 . */ + +#ifndef LDAPSERVER_H +#define LDAPSERVER_H + +#include "dirmngr.h" + +/* Release the list of SERVERS. As usual it is okay to call this + function with SERVERS passed as NULL. */ +void ldapserver_list_free (ldap_server_t servers); + + +/* Parse a single LDAP server configuration line. Returns the server + or NULL in case of errors. The configuration lineis assumed to be + colon seprated with these fields: + + 1. field: Hostname + 2. field: Portnumber + 3. field: Username + 4. field: Password + 5. field: Base DN + + FILENAME and LINENO are used for diagnostic purposes only. +*/ +ldap_server_t ldapserver_parse_one (char *line, + const char *filename, unsigned int lineno); + + +/* Iterate over all servers. */ + +struct ldapserver_iter +{ + ctrl_t ctrl; + enum { LDAPSERVER_SESSION, LDAPSERVER_OPT } group; + ldap_server_t server; +}; + + +static inline void +ldapserver_iter_next (struct ldapserver_iter *iter) +{ + if (iter->server) + iter->server = iter->server->next; + + if (! iter->server) + { + if (iter->group == LDAPSERVER_SESSION) + { + iter->group = LDAPSERVER_OPT; + iter->server = opt.ldapservers; + } + } +} + + +static inline int +ldapserver_iter_end_p (struct ldapserver_iter *iter) +{ + return (iter->group == LDAPSERVER_OPT && iter->server == NULL); +} + + +static inline void +ldapserver_iter_begin (struct ldapserver_iter *iter, ctrl_t ctrl) +{ + iter->ctrl = ctrl; + iter->group = LDAPSERVER_SESSION; + iter->server = get_ldapservers_from_ctrl (ctrl); + + while (iter->server == NULL && ! ldapserver_iter_end_p (iter)) + ldapserver_iter_next (iter); +} + +#endif /* LDAPSERVER_H */ diff --git a/dirmngr/misc.c b/dirmngr/misc.c new file mode 100644 index 000000000..040d4434a --- /dev/null +++ b/dirmngr/misc.c @@ -0,0 +1,486 @@ +/* misc.c - miscellaneous + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. + * + * 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 "util.h" +#include "misc.h" + + +/* Convert the hex encoded STRING back into binary and store the + result into the provided buffer RESULT. The actual size of that + buffer will be returned. The caller should provide RESULT of at + least strlen(STRING)/2 bytes. There is no error detection, the + parsing stops at the first non hex character. With RESULT given as + NULL, the fucntion does only return the size of the buffer which + would be needed. */ +size_t +unhexify (unsigned char *result, const char *string) +{ + const char *s; + size_t n; + + for (s=string,n=0; hexdigitp (s) && hexdigitp(s+1); s += 2) + { + if (result) + result[n] = xtoi_2 (s); + n++; + } + return n; +} + + +char* +hashify_data( const char* data, size_t len ) +{ + unsigned char buf[20]; + gcry_md_hash_buffer (GCRY_MD_SHA1, buf, data, len); + return hexify_data( buf, 20 ); +} + +char* +hexify_data( const unsigned char* data, size_t len ) +{ + int i; + char* result = xmalloc( sizeof( char ) * (2*len+1)); + + for( i = 0; i < 2*len; i+=2 ) + sprintf( result+i, "%02X", *data++); + return result; +} + +char * +serial_hex (ksba_sexp_t serial ) +{ + unsigned char* p = serial; + char *endp; + unsigned long n; + char *certid; + + if (!p) + return NULL; + else { + p++; /* ignore initial '(' */ + n = strtoul (p, (char**)&endp, 10); + p = endp; + if (*p!=':') + return NULL; + else { + int i = 0; + certid = xmalloc( sizeof( char )*(2*n + 1 ) ); + for (p++; n; n--, p++) { + sprintf ( certid+i , "%02X", *p); + i += 2; + } + } + } + return certid; +} + + +/* Take an S-Expression encoded blob and return a pointer to the + actual data as well as its length. Return NULL for an invalid + S-Expression.*/ +const unsigned char * +serial_to_buffer (const ksba_sexp_t serial, size_t *length) +{ + unsigned char *p = serial; + char *endp; + unsigned long n; + + if (!p || *p != '(') + return NULL; + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p != ':') + return NULL; + p++; + *length = n; + return p; +} + + +/* Do an in-place percent unescaping of STRING. Returns STRING. Noet + that this function does not do a '+'-to-space unescaping.*/ +char * +unpercent_string (char *string) +{ + char *s = string; + char *d = string; + + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 ( s); + s += 2; + } + else + *d++ = *s++; + } + *d = 0; + return string; +} + +/* Convert a canonical encoded S-expression in CANON into the GCRY + type. */ +gpg_error_t +canon_sexp_to_gcry (const unsigned char *canon, gcry_sexp_t *r_sexp) +{ + gpg_error_t err; + size_t n; + gcry_sexp_t sexp; + + *r_sexp = NULL; + n = gcry_sexp_canon_len (canon, 0, NULL, NULL); + if (!n) + { + log_error (_("invalid canonical S-expression found\n")); + err = gpg_error (GPG_ERR_INV_SEXP); + } + else if ((err = gcry_sexp_sscan (&sexp, NULL, canon, n))) + log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); + else + *r_sexp = sexp; + return err; +} + + +/* Return an allocated buffer with the formatted fingerprint as one + large hexnumber */ +char * +get_fingerprint_hexstring (ksba_cert_t cert) +{ + unsigned char digest[20]; + gcry_md_hd_t md; + int rc; + char *buf; + int i; + + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc)); + + rc = ksba_cert_hash (cert, 0, HASH_FNC, md); + if (rc) + { + log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc)); + 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); + buf = xmalloc (41); + *buf = 0; + for (i=0; i < 20; i++ ) + sprintf (buf+strlen(buf), "%02X", digest[i]); + return buf; +} + +/* Return an allocated buffer with the formatted fingerprint as one + large hexnumber. This version inserts the usual colons. */ +char * +get_fingerprint_hexstring_colon (ksba_cert_t cert) +{ + unsigned char digest[20]; + gcry_md_hd_t md; + int rc; + char *buf; + int i; + + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc)); + + rc = ksba_cert_hash (cert, 0, HASH_FNC, md); + if (rc) + { + log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc)); + 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); + buf = xmalloc (61); + *buf = 0; + for (i=0; i < 20; i++ ) + sprintf (buf+strlen(buf), "%02X:", digest[i]); + buf[strlen(buf)-1] = 0; /* Remove railing colon. */ + return buf; +} + + +/* Dump the serial number SERIALNO to the log stream. */ +void +dump_serial (ksba_sexp_t serialno) +{ + char *p; + + p = serial_hex (serialno); + log_printf ("%s", p?p:"?"); + xfree (p); +} + + +/* Dump STRING to the log file but choose the best readable + format. */ +void +dump_string (const char *string) +{ + + if (!string) + log_printf ("[error]"); + else + { + const unsigned char *s; + + for (s=string; *s; s++) + { + if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) + break; + } + if (!*s && *string != '[') + log_printf ("%s", string); + else + { + log_printf ( "[ "); + log_printhex (NULL, string, strlen (string)); + log_printf ( " ]"); + } + } +} + +/* Dump an KSBA cert object to the log stream. Prefix the output with + TEXT. This is used for debugging. */ +void +dump_cert (const char *text, ksba_cert_t cert) +{ + ksba_sexp_t sexp; + char *p; + ksba_isotime_t t; + + log_debug ("BEGIN Certificate `%s':\n", text? text:""); + if (cert) + { + sexp = ksba_cert_get_serial (cert); + p = serial_hex (sexp); + log_debug (" serial: %s\n", p?p:"?"); + xfree (p); + ksba_free (sexp); + + ksba_cert_get_validity (cert, 0, t); + log_debug (" notBefore: "); + dump_isotime (t); + log_printf ("\n"); + ksba_cert_get_validity (cert, 1, t); + log_debug (" notAfter: "); + dump_isotime (t); + log_printf ("\n"); + + p = ksba_cert_get_issuer (cert, 0); + log_debug (" issuer: "); + dump_string (p); + ksba_free (p); + log_printf ("\n"); + + p = ksba_cert_get_subject (cert, 0); + log_debug (" subject: "); + dump_string (p); + ksba_free (p); + log_printf ("\n"); + + log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); + + p = get_fingerprint_hexstring (cert); + log_debug (" SHA1 fingerprint: %s\n", p); + xfree (p); + } + log_debug ("END Certificate\n"); +} + + + +/* Log the certificate's name in "#SN/ISSUERDN" format along with + TEXT. */ +void +cert_log_name (const char *text, ksba_cert_t cert) +{ + log_info ("%s", text? text:"certificate" ); + if (cert) + { + ksba_sexp_t sn; + char *p; + + p = ksba_cert_get_issuer (cert, 0); + sn = ksba_cert_get_serial (cert); + if (p && sn) + { + log_printf (" #"); + dump_serial (sn); + log_printf ("/"); + dump_string (p); + } + else + log_printf (" [invalid]"); + ksba_free (sn); + xfree (p); + } + log_printf ("\n"); +} + + +/* Log the certificate's subject DN along with TEXT. */ +void +cert_log_subject (const char *text, ksba_cert_t cert) +{ + log_info ("%s", text? text:"subject" ); + if (cert) + { + char *p; + + p = ksba_cert_get_subject (cert, 0); + if (p) + { + log_printf (" /"); + dump_string (p); + xfree (p); + } + else + log_printf (" [invalid]"); + } + log_printf ("\n"); +} + + +/**************** + * Remove all %xx escapes; this is done inplace. + * Returns: New length of the string. + */ +static int +remove_percent_escapes (unsigned char *string) +{ + int n = 0; + unsigned char *p, *s; + + for (p = s = string; *s; s++) + { + if (*s == '%') + { + if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) + { + s++; + *p = xtoi_2 (s); + s++; + p++; + n++; + } + else + { + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p = 0; + return -1; /* Bad URI. */ + } + } + else + { + *p++ = *s; + n++; + } + } + *p = 0; /* Always keep a string terminator. */ + return n; +} + + +/* Return the host name and the port (0 if none was given) from the + URL. Return NULL on error or if host is not included in the + URL. */ +char * +host_and_port_from_url (const char *url, int *port) +{ + const char *s, *s2; + char *buf, *p; + int n; + + s = url; + + *port = 0; + + /* Find the scheme */ + if ( !(s2 = strchr (s, ':')) || s2 == s ) + return NULL; /* No scheme given. */ + s = s2+1; + + /* Find the hostname */ + if (*s != '/') + return NULL; /* Does not start with a slash. */ + + s++; + if (*s != '/') + return NULL; /* No host name. */ + s++; + + buf = xtrystrdup (s); + if (!buf) + { + log_error (_("malloc failed: %s\n"), strerror (errno)); + return NULL; + } + if ((p = strchr (buf, '/'))) + *p++ = 0; + strlwr (buf); + if ((p = strchr (p, ':'))) + { + *p++ = 0; + *port = atoi (p); + } + + /* Remove quotes and make sure that no Nul has been encoded. */ + if ((n = remove_percent_escapes (buf)) < 0 + || n != strlen (buf) ) + { + log_error (_("bad URL encoding detected\n")); + xfree (buf); + return NULL; + } + + return buf; +} + diff --git a/dirmngr/misc.h b/dirmngr/misc.h new file mode 100644 index 000000000..b721549ec --- /dev/null +++ b/dirmngr/misc.h @@ -0,0 +1,87 @@ +/* misc.h - miscellaneous + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * 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 + */ + +#ifndef MISC_H +#define MISC_H + +/* Convert hex encoded string back to binary. */ +size_t unhexify (unsigned char *result, const char *string); + +/* Returns SHA1 hash of the data. */ +char* hashify_data( const char* data, size_t len ); + +/* Returns data as a hex string. */ +char* hexify_data( const unsigned char* data, size_t len ); + +/* Returns the serial number as a hex string. */ +char* serial_hex ( ksba_sexp_t serial ); + +/* Take an S-Expression encoded blob and return a pointer to the + actual data as well as its length. */ +const unsigned char *serial_to_buffer (const ksba_sexp_t serial, + size_t *length); + +/* Do an in-place percent unescaping of STRING. Returns STRING. */ +char *unpercent_string (char *string); + +gpg_error_t canon_sexp_to_gcry (const unsigned char *canon, + gcry_sexp_t *r_sexp); + +/* Return an allocated hex-string with the SHA-1 fingerprint of + CERT. */ +char *get_fingerprint_hexstring (ksba_cert_t cert); +/* Return an allocated hex-string with the SHA-1 fingerprint of + CERT. This version inserts the usual colons. */ +char *get_fingerprint_hexstring_colon (ksba_cert_t cert); + +/* Log CERT in short format with s/n and issuer DN prefixed by TEXT. */ +void cert_log_name (const char *text, ksba_cert_t cert); + +/* Log CERT in short format with the subject DN prefixed by TEXT. */ +void cert_log_subject (const char *text, ksba_cert_t cert); + +/* Dump the serial number SERIALNO to the log stream. */ +void dump_serial (ksba_sexp_t serialno); + +/* Dump STRING to the log file but choose the best readable + format. */ +void dump_string (const char *string); + +/* Dump an KSBA cert object to the log stream. Prefix the output with + TEXT. This is used for debugging. */ +void dump_cert (const char *text, ksba_cert_t cert); + +/* Return the host name and the port (0 if none was given) from the + URL. Return NULL on error or if host is not included in the + URL. */ +char *host_and_port_from_url (const char *url, int *port); + + +#ifdef HAVE_FOPENCOOKIE +/* We have to implement funopen in terms of glibc's fopencookie. */ +FILE *funopen(void *cookie, + int (*readfn)(void *, char *, int), + int (*writefn)(void *, const char *, int), + fpos_t (*seekfn)(void *, fpos_t, int), + int (*closefn)(void *)); +#endif /*HAVE_FOPENCOOKIE*/ + + +#endif /* MISC_H */ diff --git a/dirmngr/no-libgcrypt.c b/dirmngr/no-libgcrypt.c new file mode 100644 index 000000000..fbbfd40ed --- /dev/null +++ b/dirmngr/no-libgcrypt.c @@ -0,0 +1,154 @@ +/* no-libgcrypt.c - Replacement functions for libgcrypt. + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is free software; as a special exception the author gives + * unlimited permission to copy and/or distribute it, with or without + * modifications, as long as this notice is preserved. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY, to the extent permitted by law; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. + */ + +#include +#include +#include +#include +#include + +#include "../common/util.h" +#include "i18n.h" + + +/* Replace libgcrypt's malloc functions which are used by + ../jnlib/libjnlib.a . ../common/util.h defines macros to map them + to xmalloc etc. */ +static void +out_of_memory (void) +{ + log_fatal (_("error allocating enough memory: %s\n"), strerror (errno)); +} + + +void * +gcry_malloc (size_t n) +{ + return malloc (n); +} + +void * +gcry_malloc_secure (size_t n) +{ + return malloc (n); +} + +void * +gcry_xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + out_of_memory (); + return p; +} + +char * +gcry_strdup (const char *string) +{ + char *p = malloc (strlen (string)+1); + if (p) + strcpy (p, string); + return p; +} + + +void * +gcry_realloc (void *a, size_t n) +{ + return realloc (a, n); +} + +void * +gcry_xrealloc (void *a, size_t n) +{ + void *p = realloc (a, n); + if (!p) + out_of_memory (); + return p; +} + + + +void * +gcry_calloc (size_t n, size_t m) +{ + return calloc (n, m); +} + +void * +gcry_xcalloc (size_t n, size_t m) +{ + void *p = calloc (n, m); + if (!p) + out_of_memory (); + return p; +} + + +char * +gcry_xstrdup (const char *string) +{ + void *p = malloc (strlen (string)+1); + if (!p) + out_of_memory (); + strcpy( p, string ); + return p; +} + +void +gcry_free (void *a) +{ + if (a) + free (a); +} + + +/* We need this dummy because exechelp.c uses gcry_control to + terminate the secure memeory. */ +gcry_error_t +gcry_control (enum gcry_ctl_cmds cmd, ...) +{ + (void)cmd; + return 0; +} + +void +gcry_set_outofcore_handler (gcry_handler_no_mem_t h, void *opaque) +{ + (void)h; + (void)opaque; +} + +void +gcry_set_fatalerror_handler (gcry_handler_error_t fnc, void *opaque) +{ + (void)fnc; + (void)opaque; +} + +void +gcry_set_log_handler (gcry_handler_log_t f, void *opaque) +{ + (void)f; + (void)opaque; +} + + +void +gcry_create_nonce (void *buffer, size_t length) +{ + (void)buffer; + (void)length; + + log_fatal ("unexpected call to gcry_create_nonce\n"); +} diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c new file mode 100644 index 000000000..a8db51d17 --- /dev/null +++ b/dirmngr/ocsp.c @@ -0,0 +1,799 @@ +/* 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" +#include "estream.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 succesfully 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 reponse. */ +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.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, + (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) + |HTTP_FLAG_NEED_HEADER, + opt.http_proxy, + 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? "superseeded": + 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/ocsp.h b/dirmngr/ocsp.h new file mode 100644 index 000000000..cfab7dd6f --- /dev/null +++ b/dirmngr/ocsp.h @@ -0,0 +1,31 @@ +/* ocsp.h - OCSP management + * Copyright (C) 2003 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef OCSP_H +#define OCSP_H + +gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, + int force_default_responder); + +/* Release the list of OCSP certificates hold in the CTRL object. */ +void release_ctrl_ocsp_certs (ctrl_t ctrl); + +#endif /*OCSP_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c new file mode 100644 index 000000000..a7b623cb1 --- /dev/null +++ b/dirmngr/server.c @@ -0,0 +1,1539 @@ +/* dirmngr.c - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009 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 +#include + +#define JNLIB_NEED_LOG_LOGV +#include "dirmngr.h" +#include + +#include "crlcache.h" +#include "crlfetch.h" +#include "ldapserver.h" +#include "ocsp.h" +#include "certcache.h" +#include "validate.h" +#include "misc.h" + +/* To avoid DoS attacks we limit the size of a certificate to + something reasonable. */ +#define MAX_CERT_LENGTH (8*1024) + +#define PARM_ERROR(t) assuan_set_error (ctx, \ + gpg_error (GPG_ERR_ASS_PARAMETER), (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 serfver. */ + ldap_server_t ldapservers; +}; + + + + +/* 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; +} + + + +/* 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; +} + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + +/* Same as has_option but only considers options at the begin of the + line. This is useful for commands which allow arbitrary strings on + the line. */ +static int +has_leading_option (const char *line, const char *name) +{ + const char *s; + int n; + + if (name[0] != '-' || name[1] != '-' || !name[2] || spacep (name+2)) + return 0; + n = strlen (name); + while ( *line == '-' && line[1] == '-' ) + { + s = line; + while (*line && !spacep (line)) + line++; + if (n == (line - s) && !strncmp (s, name, n)) + return 1; + while (spacep (line)) + line++; + } + return 0; +} + + +/* Same as has_option but does only test for the name of the option + and ignores an argument, i.e. with NAME being "--hash" it would + return a pointer for "--hash" as well as for "--hash=foo". If + thhere is no such option NULL is returned. The pointer returned + points right behind the option name, this may be an equal sign, Nul + or a space. */ +/* static const char * */ +/* has_option_name (const char *line, const char *name) */ +/* { */ +/* const char *s; */ +/* int n = strlen (name); */ + +/* s = strstr (line, name); */ +/* return (s && (s == line || spacep (s-1)) */ +/* && (!s[n] || spacep (s+n) || s[n] == '=')) ? (s+n) : NULL; */ +/* } */ + + +/* Skip over options. It is assumed that leading spaces have been + removed (this is the case for lines passed to a handler from + assuan). Blanks after the options are also removed. */ +static char * +skip_options (char *line) +{ + while ( *line == '-' && line[1] == '-' ) + { + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + } + return line; +} + + +/* 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); + + 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 + return gpg_error (GPG_ERR_UNKNOWN_OPTION); + + return 0; +} + +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) +{ + 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 PARM_ERROR (_("ldapserver missing")); + + server = ldapserver_parse_one (line, "", 0); + if (! server) + return 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 0; +} + + +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 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"); + } + } + + if (err) + log_error (_("command %s failed: %s\n"), "ISVALID", gpg_strerror (err)); + xfree (issuerhash); + return 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: + if (err) + log_error (_("command %s failed: %s\n"), "CHECKCRL", gpg_strerror (err)); + ksba_cert_release (cert); + return 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 certifciates.\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: + if (err) + log_error (_("command %s failed: %s\n"), "CHECKOCSP", gpg_strerror (err)); + ksba_cert_release (cert); + return 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) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + 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; + unsigned char *value = NULL; + size_t valuelen; + struct ldapserver_iter ldapserver_iter; + cert_fetch_context_t fetch_context; + 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. */ + 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); + } + + 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); + + if (err) + log_error (_("command %s failed: %s\n"), "LOOKUP", gpg_strerror (err)); + + return 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); + } + } + + if (err) + log_error (_("command %s failed: %s\n"), "LOADCRL", gpg_strerror (err)); + return 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; + FILE *fp = assuan_get_data_fp (ctx); + + (void)line; + + if (!fp) + return PARM_ERROR (_("no data stream")); + + err = crl_cache_list (fp); + if (err) + log_error (_("command %s failed: %s\n"), "LISTCRLS", gpg_strerror (err)); + return 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 wnats 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: + if (err) + log_error (_("command %s failed: %s\n"), "CACHECERT", gpg_strerror (err)); + ksba_cert_release (cert); + return 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: + if (err) + log_error (_("command %s failed: %s\n"), "VALIDATE", gpg_strerror (err)); + ksba_cert_release (cert); + return err; +} + + + +/* 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[] = { + { "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 }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { 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; +} + + +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + (void)line; + + ldapserver_list_free (ctrl->server_local->ldapservers); + ctrl->server_local->ldapservers = NULL; + return 0; +} + + +/* Startup the server and run the main command loop. With FD = -1 + used 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) + { + size_t n; + const char *cfgname; + + cfgname = opt.config_filename? opt.config_filename : "[none]"; + + n = (30 + strlen (opt.homedir) + strlen (cfgname) + + strlen (hello) + 1); + hello_line = xmalloc (n+1); + snprintf (hello_line, n, + "Home: %s\n" + "Config: %s\n" + "%s", + opt.homedir, + cfgname, + hello); + hello_line[n] = 0; + } + + 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; + } + } + + ldap_wrapper_connection_cleanup (ctrl); + + ldapserver_list_free (ctrl->server_local->ldapservers); + ctrl->server_local->ldapservers = NULL; + + ctrl->server_local->assuan_ctx = NULL; + assuan_release (ctx); + + 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); + xfree (ctrl); + } +} + + +/* Send a status line back to the client. KEYWORD is the status + keyword, the optioal string argumenst 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; +} + + +/* Note, that we ignore CTRL for now but use the first connection to + send the progress info back. */ +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 new file mode 100644 index 000000000..538779842 --- /dev/null +++ b/dirmngr/validate.c @@ -0,0 +1,1160 @@ +/* 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. + + 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 arbitary 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_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 hierachy 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 have not " + "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 have not " + "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 have not " + "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 have not been used " + "for encryption\n"): + mode==2? _("certificate should have not 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/dirmngr/validate.h b/dirmngr/validate.h new file mode 100644 index 000000000..0d9283c04 --- /dev/null +++ b/dirmngr/validate.h @@ -0,0 +1,55 @@ +/* validate.h - Certificate validation + * Copyright (C) 2004 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 + */ + +#ifndef VALIDATE_H +#define VALIDATE_H + + +enum { + /* Simple certificate validation mode. */ + VALIDATE_MODE_CERT = 0, + /* Standard CRL issuer certificate validation; i.e. CRLs are not + considered for CRL issuer certificates. */ + VALIDATE_MODE_CRL = 1, + /* Full CRL validation. */ + VALIDATE_MODE_CRL_RECURSIVE = 2, + /* Validation as used for OCSP. */ + VALIDATE_MODE_OCSP = 3 +}; + + +/* Validate the certificate CHAIN up to the trust anchor. Optionally + return the closest expiration time in R_EXPTIME. */ +gpg_error_t validate_cert_chain (ctrl_t ctrl, + ksba_cert_t cert, ksba_isotime_t r_exptime, + int mode, char **r_trust_anchor); + +/* Return 0 if the certificate CERT is usable for certification. */ +gpg_error_t cert_use_cert_p (ksba_cert_t cert); + +/* Return 0 if the certificate CERT is usable for signing OCSP + responses. */ +gpg_error_t cert_use_ocsp_p (ksba_cert_t cert); + +/* Return 0 if the certificate CERT is usable for signing CRLs. */ +gpg_error_t cert_use_crl_p (ksba_cert_t cert); + + +#endif /*VALIDATE_H*/ diff --git a/g13/be-encfs.c b/g13/be-encfs.c index 524f09e6b..fb695e1be 100644 --- a/g13/be-encfs.c +++ b/g13/be-encfs.c @@ -304,7 +304,8 @@ run_encfs_tool (ctrl_t ctrl, enum encfs_cmds cmd, close (outbound[1]); if (pid != (pid_t)(-1)) { - gnupg_wait_process (pgmname, pid, NULL); + gnupg_wait_process (pgmname, pid, 0, NULL); + gnupg_release_process (pid); } runner_release (runner); encfs_handler_cleanup (parm); diff --git a/g13/runner.c b/g13/runner.c index 18293e10c..3ffe523ca 100644 --- a/g13/runner.c +++ b/g13/runner.c @@ -165,7 +165,8 @@ runner_release (runner_t runner) arbitrary NAME of the runner object. However it does not matter because that information is only used for diagnostics.) */ - gnupg_wait_process (runner->name, runner->pid, NULL); + gnupg_wait_process (runner->name, runner->pid, 0, NULL); + gnupg_release_process (runner->pid); } xfree (runner->name); @@ -370,7 +371,8 @@ runner_thread (void *arg) int exitcode; log_debug ("runner thread waiting ...\n"); - err = gnupg_wait_process (runner->name, runner->pid, &exitcode); + err = gnupg_wait_process (runner->name, runner->pid, 0, &exitcode); + gnupg_release_process (runner->pid); runner->pid = (pid_t)(-1); if (err) log_error ("running `%s' failed (exitcode=%d): %s\n", diff --git a/m4/ChangeLog b/m4/ChangeLog index ee3bc29b3..b93fa2d3c 100644 --- a/m4/ChangeLog +++ b/m4/ChangeLog @@ -1,3 +1,7 @@ +2010-06-08 Werner Koch + + * ldap.m4 (gnupg_have_ldap): Set variable. + 2009-09-03 Werner Koch * estream.m4: Update for libestream. diff --git a/m4/ldap.m4 b/m4/ldap.m4 index f4462ca0d..954f88ad6 100644 --- a/m4/ldap.m4 +++ b/m4/ldap.m4 @@ -17,7 +17,7 @@ AC_DEFUN([GNUPG_CHECK_LDAP], # OpenLDAP, circa 1999, was terrible with creating weird dependencies. # If all else fails, the user can play guess-the-dependency by using # something like ./configure LDAPLIBS="-Lfoo -lbar" - +gnupg_have_ldap=no AC_ARG_WITH(ldap, AC_HELP_STRING([--with-ldap=DIR],[look for the LDAP library in DIR]), [_ldap_with=$withval]) @@ -66,6 +66,7 @@ if test x$_ldap_with != xno ; then test "$gnupg_cv_func_ldaplber_init" = yes ; then LDAPLIBS="$LDAP_LDFLAGS $MY_LDAPLIBS" GPGKEYS_LDAP="gpg2keys_ldap$EXEEXT" + gnupg_have_ldap=yes AC_CHECK_FUNCS(ldap_get_option ldap_set_option) # The extra test for ldap_start_tls_sA is for W32 because diff --git a/scd/ChangeLog b/scd/ChangeLog index 3bb00f178..7578b7fc2 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,8 @@ +2010-06-09 Werner Koch + + * scdaemon.c (main): s/log_set_get_tid_callback/log_set_pid_suffix_cb/. + (tid_log_callback): Adjust for this change. + 2010-03-11 Werner Koch * scdaemon.c: Include "asshelp.h". diff --git a/scd/scdaemon.c b/scd/scdaemon.c index e5d8ea911..93fc58a93 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -269,14 +269,15 @@ my_strusage (int level) } -static unsigned long -tid_log_callback (void) +static int +tid_log_callback (unsigned long *rvalue) { #ifdef PTH_HAVE_PTH_THREAD_ID - return pth_thread_id (); + *rvalue = pth_thread_id (); #else - return (unsigned long)pth_self (); + *rvalue = (unsigned long)pth_self (); #endif + return 2; /* Use use hex representation. */ } @@ -551,7 +552,7 @@ main (int argc, char **argv ) break; case oDebugDisableTicker: ticker_disabled = 1; break; case oDebugLogTid: - log_set_get_tid_callback (tid_log_callback); + log_set_pid_suffix_cb (tid_log_callback); break; case oOptions: diff --git a/sm/export.c b/sm/export.c index 61d827b9b..2ded1ff62 100644 --- a/sm/export.c +++ b/sm/export.c @@ -715,8 +715,9 @@ export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, es_fclose (fp); if (pid != -1) { - if (!gnupg_wait_process (pgmname, pid, NULL)) + if (!gnupg_wait_process (pgmname, pid, 0, NULL)) child_err = 0; + gnupg_release_process (pid); } if (!err) err = child_err; diff --git a/sm/import.c b/sm/import.c index 71cefff65..6a012ca66 100644 --- a/sm/import.c +++ b/sm/import.c @@ -773,8 +773,9 @@ parse_p12 (ctrl_t ctrl, ksba_reader_t reader, es_fclose (fp); if (pid != -1) { - if (!gnupg_wait_process (pgmname, pid, NULL)) + if (!gnupg_wait_process (pgmname, pid, 0, NULL)) child_err = 0; + gnupg_release_process (pid); } if (!err) err = child_err; diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 8f9278d28..505ca465a 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1051,10 +1051,11 @@ gpg_agent_runtime_change (void) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) - err = gnupg_wait_process (pgmname, pid, NULL); + err = gnupg_wait_process (pgmname, pid, 0, NULL); if (err) gc_error (0, 0, "error running `%s%s': %s", pgmname, " reloadagent", gpg_strerror (err)); + gnupg_release_process (pid); #endif /*!HAVE_W32_SYSTEM*/ } @@ -1082,10 +1083,11 @@ scdaemon_runtime_change (void) err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); if (!err) - err = gnupg_wait_process (pgmname, pid, NULL); + err = gnupg_wait_process (pgmname, pid, 0, NULL); if (err) gc_error (0, 0, "error running `%s%s': %s", pgmname, " scd killscd", gpg_strerror (err)); + gnupg_release_process (pid); } @@ -1501,13 +1503,14 @@ gc_component_check_options (int component, FILE *out, const char *conf_file) close (filedes[1]); errlines = collect_error_output (filedes[0], gc_component[component].name); - if (gnupg_wait_process (pgmname, pid, &exitcode)) + if (gnupg_wait_process (pgmname, pid, 0, &exitcode)) { if (exitcode == -1) result |= 1; /* Program could not be run or it terminated abnormally. */ result |= 2; /* Program returned an error. */ } + gnupg_release_process (pid); } /* If the program could not be run, we can't tell whether @@ -1919,10 +1922,11 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend) if (fclose (config) && ferror (config)) gc_error (1, errno, "error closing %s", pgmname); - err = gnupg_wait_process (pgmname, pid, &exitcode); + err = gnupg_wait_process (pgmname, pid, 0, &exitcode); if (err) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); + gnupg_release_process (pid); /* At this point, we can parse the configuration file. */