mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-23 10:29:58 +01:00
26d7e0d7ac
* common/logging.h: Rename JNLIB_LOG_* to GPGRT_LOG_*. * common/mischelp.h: Rename JNLIB_GCC_* to GPGRT_GCC_*. -- JNLIB has no more meaning. Thus we switch to a GPGRT_ prefix in anticipation that some code may eventually be moved to libgpg-error. Signed-off-by: Werner Koch <wk@gnupg.org>
1033 lines
26 KiB
C
1033 lines
26 KiB
C
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
#include <gpg-error.h>
|
|
#include <assuan.h>
|
|
|
|
#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"
|
|
#include "init.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_("expect certificates 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;
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
early_system_init ();
|
|
set_strusage (my_strusage);
|
|
log_set_prefix ("dirmngr-client",
|
|
GPGRT_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. */
|
|
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_NAME);
|
|
if (only_daemon && (!infostr || !*infostr))
|
|
{
|
|
if (dirmngr_user_socket_name ())
|
|
infostr = xstrdup (dirmngr_user_socket_name ());
|
|
else
|
|
infostr = xstrdup (dirmngr_sys_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];
|
|
assuan_fd_t 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 (es_fileno (es_stderr));
|
|
no_close_list[i] = ASSUAN_INVALID_FD;
|
|
|
|
/* 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 %s environment variable\n"),
|
|
DIRMNGR_INFO_NAME);
|
|
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;
|
|
}
|