mirror of
git://git.gnupg.org/gnupg.git
synced 2025-01-03 12:11:33 +01:00
All standard keyserver commands are now using dirmngr.
This commit is contained in:
parent
357f8d5398
commit
7f32d88ed1
@ -1,3 +1,14 @@
|
||||
2011-01-20 Werner Koch <wk@g10code.com>
|
||||
|
||||
* util.h (struct b64state): Add field LASTERR.
|
||||
* b64enc.c (enc_start, b64enc_write, b64enc_finish): Handle
|
||||
LASTERR. This is to make sure that we don't leak strduped data.
|
||||
* b64dec.c (b64dec_start, b64dec_proc, b64dec_finish): Ditto.
|
||||
|
||||
* http.c (escape_data): New.
|
||||
(insert_escapes): Implement using escape_data.
|
||||
(http_escape_data): New.
|
||||
|
||||
2011-01-18 Werner Koch <wk@g10code.com>
|
||||
|
||||
* iobuf.c (file_es_filter_ctx_t): New.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* b64dec.c - Simple Base64 decoder.
|
||||
* Copyright (C) 2008 Free Software Foundation, Inc.
|
||||
* Copyright (C) 2008, 2011 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
@ -72,16 +72,19 @@ b64dec_start (struct b64state *state, const char *title)
|
||||
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;
|
||||
state->lasterr = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
||||
else
|
||||
{
|
||||
state->title = xtrystrdup (title);
|
||||
if (!state->title)
|
||||
state->lasterr = gpg_error_from_syserror ();
|
||||
else
|
||||
state->idx = s_init;
|
||||
}
|
||||
}
|
||||
else
|
||||
state->idx = s_b64_0;
|
||||
return 0;
|
||||
return state->lasterr;
|
||||
}
|
||||
|
||||
|
||||
@ -96,12 +99,18 @@ b64dec_proc (struct b64state *state, void *buffer, size_t length,
|
||||
int pos = state->quad_count;
|
||||
char *d, *s;
|
||||
|
||||
if (state->lasterr)
|
||||
return state->lasterr;
|
||||
|
||||
if (state->stop_seen)
|
||||
{
|
||||
*r_nbytes = 0;
|
||||
return gpg_error (GPG_ERR_EOF);
|
||||
state->lasterr = gpg_error (GPG_ERR_EOF);
|
||||
xfree (state->title);
|
||||
state->title = NULL;
|
||||
return state->lasterr;
|
||||
}
|
||||
|
||||
|
||||
for (s=d=buffer; length && !state->stop_seen; length--, s++)
|
||||
{
|
||||
switch (ds)
|
||||
@ -210,6 +219,9 @@ b64dec_proc (struct b64state *state, void *buffer, size_t length,
|
||||
gpg_error_t
|
||||
b64dec_finish (struct b64state *state)
|
||||
{
|
||||
if (state->lasterr)
|
||||
return state->lasterr;
|
||||
|
||||
xfree (state->title);
|
||||
state->title = NULL;
|
||||
return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* b64enc.c - Simple Base64 encoder.
|
||||
* Copyright (C) 2001, 2003, 2004, 2008, 2010 Free Software Foundation, Inc.
|
||||
* Copyright (C) 2001, 2003, 2004, 2008, 2010,
|
||||
* 2011 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
@ -143,6 +144,7 @@ enc_start (struct b64state *state, FILE *fp, estream_t stream,
|
||||
memset (state, 0, sizeof *state);
|
||||
state->fp = fp;
|
||||
state->stream = stream;
|
||||
state->lasterr = 0;
|
||||
if (title && !*title)
|
||||
state->flags |= B64ENC_NO_LINEFEEDS;
|
||||
else if (title)
|
||||
@ -154,9 +156,9 @@ enc_start (struct b64state *state, FILE *fp, estream_t stream,
|
||||
}
|
||||
state->title = xtrystrdup (title);
|
||||
if (!state->title)
|
||||
return gpg_error_from_syserror ();
|
||||
state->lasterr = gpg_error_from_syserror ();
|
||||
}
|
||||
return 0;
|
||||
return state->lasterr;
|
||||
}
|
||||
|
||||
|
||||
@ -203,6 +205,8 @@ b64enc_write (struct b64state *state, const void *buffer, size_t nbytes)
|
||||
int idx, quad_count;
|
||||
const unsigned char *p;
|
||||
|
||||
if (state->lasterr)
|
||||
return state->lasterr;
|
||||
|
||||
if (!nbytes)
|
||||
{
|
||||
@ -285,7 +289,13 @@ b64enc_write (struct b64state *state, const void *buffer, size_t nbytes)
|
||||
return 0;
|
||||
|
||||
write_error:
|
||||
return gpg_error_from_syserror ();
|
||||
state->lasterr = gpg_error_from_syserror ();
|
||||
if (state->title)
|
||||
{
|
||||
xfree (state->title);
|
||||
state->title = NULL;
|
||||
}
|
||||
return state->lasterr;
|
||||
}
|
||||
|
||||
|
||||
@ -297,6 +307,9 @@ b64enc_finish (struct b64state *state)
|
||||
int idx, quad_count;
|
||||
char tmp[4];
|
||||
|
||||
if (state->lasterr)
|
||||
return state->lasterr;
|
||||
|
||||
if (!(state->flags & B64ENC_DID_HEADER))
|
||||
goto cleanup;
|
||||
|
||||
@ -404,6 +417,7 @@ b64enc_finish (struct b64state *state)
|
||||
}
|
||||
state->fp = NULL;
|
||||
state->stream = NULL;
|
||||
state->lasterr = err;
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* http.c - HTTP protocol handler
|
||||
* Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006,
|
||||
* 2009, 2010 Free Software Foundation, Inc.
|
||||
* Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
|
||||
* 2011 Free Software Foundation, Inc.
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
@ -742,14 +742,14 @@ remove_escapes (char *string)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
insert_escapes (char *buffer, const char *string,
|
||||
const char *special)
|
||||
static size_t
|
||||
escape_data (char *buffer, const void *data, size_t datalen,
|
||||
const char *special)
|
||||
{
|
||||
const unsigned char *s = (const unsigned char*)string;
|
||||
int n = 0;
|
||||
const unsigned char *s;
|
||||
size_t n = 0;
|
||||
|
||||
for (; *s; s++)
|
||||
for (s = data; datalen; s++, datalen--)
|
||||
{
|
||||
if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
|
||||
{
|
||||
@ -771,6 +771,14 @@ insert_escapes (char *buffer, const char *string,
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
insert_escapes (char *buffer, const char *string,
|
||||
const char *special)
|
||||
{
|
||||
return escape_data (buffer, string, strlen (string), special);
|
||||
}
|
||||
|
||||
|
||||
/* 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
|
||||
@ -792,6 +800,27 @@ http_escape_string (const char *string, const char *specials)
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Allocate a new string from {DATA,DATALEN} 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_data (const void *data, size_t datalen, const char *specials)
|
||||
{
|
||||
int n;
|
||||
char *buf;
|
||||
|
||||
n = escape_data (NULL, data, datalen, specials);
|
||||
buf = xtrymalloc (n+1);
|
||||
if (buf)
|
||||
{
|
||||
escape_data (buf, data, datalen, specials);
|
||||
buf[n] = 0;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static uri_tuple_t
|
||||
|
@ -117,6 +117,7 @@ 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);
|
||||
char *http_escape_data (const void *data, size_t datalen, const char *specials);
|
||||
|
||||
|
||||
#endif /*GNUPG_COMMON_HTTP_H*/
|
||||
|
@ -150,6 +150,7 @@ struct b64state
|
||||
u32 crc;
|
||||
int stop_seen:1;
|
||||
int invalid_encoding:1;
|
||||
gpg_error_t lasterr;
|
||||
};
|
||||
|
||||
gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title);
|
||||
|
@ -1,13 +1,9 @@
|
||||
2011-01-06 Werner Koch <wk@g10code.com>
|
||||
2011-01-20 Werner Koch <wk@g10code.com>
|
||||
|
||||
* server.c (release_ctrl_keyservers): New.
|
||||
(cmd_keyserver): New.
|
||||
|
||||
(cmd_keyserver, cmd_ks_seach, cmd_ks_get, cmd_ks_put): New.
|
||||
* dirmngr.h (uri_item_t): New.
|
||||
(struct server_control_s): Add field KEYSERVERS.
|
||||
|
||||
2011-01-04 Werner Koch <wk@g10code.com>
|
||||
|
||||
* ks-engine-hkp.c: New.
|
||||
* ks-engine.h: New.
|
||||
* ks-action.c, ks-action.h: New.
|
||||
|
@ -90,7 +90,7 @@ ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
|
||||
}
|
||||
|
||||
|
||||
/* Get the requested keys (macthing PATTERNS) using all configured
|
||||
/* Get the requested keys (matching PATTERNS) using all configured
|
||||
keyservers and write the result to the provided output stream. */
|
||||
gpg_error_t
|
||||
ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
|
||||
@ -148,3 +148,36 @@ ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN}
|
||||
is expected in OpenPGP binary transport format. */
|
||||
gpg_error_t
|
||||
ks_action_put (ctrl_t ctrl, const void *data, size_t datalen)
|
||||
{
|
||||
gpg_error_t err = 0;
|
||||
gpg_error_t first_err = 0;
|
||||
int any = 0;
|
||||
uri_item_t uri;
|
||||
|
||||
for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
|
||||
{
|
||||
if (uri->parsed_uri->is_http)
|
||||
{
|
||||
any = 1;
|
||||
err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen);
|
||||
if (err)
|
||||
{
|
||||
first_err = err;
|
||||
err = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!any)
|
||||
err = gpg_error (GPG_ERR_NO_KEYSERVER);
|
||||
else if (!err && first_err)
|
||||
err = first_err; /* fixme: Do we really want to do that? */
|
||||
return err;
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
gpg_error_t ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp);
|
||||
gpg_error_t ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp);
|
||||
gpg_error_t ks_action_put (ctrl_t ctrl, const void *data, size_t datalen);
|
||||
|
||||
|
||||
#endif /*DIRMNGR_KS_ACTION_H*/
|
||||
|
@ -38,9 +38,12 @@
|
||||
|
||||
|
||||
/* Send an HTTP request. On success returns an estream object at
|
||||
R_FP. HOSTPORTSTR is only used for diagnostics. */
|
||||
R_FP. HOSTPORTSTR is only used for diagnostics. If POST_CB is not
|
||||
NULL a post request is used and that callback is called to allow
|
||||
writing the post data. */
|
||||
static gpg_error_t
|
||||
send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
|
||||
gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
|
||||
estream_t *r_fp)
|
||||
{
|
||||
gpg_error_t err;
|
||||
@ -51,7 +54,9 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
|
||||
|
||||
*r_fp = NULL;
|
||||
once_more:
|
||||
err = http_open (&http, HTTP_REQ_GET, request,
|
||||
err = http_open (&http,
|
||||
post_cb? HTTP_REQ_POST : HTTP_REQ_GET,
|
||||
request,
|
||||
/* fixme: AUTH */ NULL,
|
||||
0,
|
||||
/* fixme: proxy*/ NULL,
|
||||
@ -65,9 +70,14 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
|
||||
we're good with both HTTP 1.0 and 1.1. */
|
||||
es_fputs ("Pragma: no-cache\r\n"
|
||||
"Cache-Control: no-cache\r\n", fp);
|
||||
http_start_data (http);
|
||||
if (es_ferror (fp))
|
||||
err = gpg_error_from_syserror ();
|
||||
if (post_cb)
|
||||
err = post_cb (post_cb_value, http);
|
||||
if (!err)
|
||||
{
|
||||
http_start_data (http);
|
||||
if (es_ferror (fp))
|
||||
err = gpg_error_from_syserror ();
|
||||
}
|
||||
}
|
||||
if (err)
|
||||
{
|
||||
@ -135,19 +145,76 @@ send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
|
||||
|
||||
/* Return the read stream and close the HTTP context. */
|
||||
*r_fp = fp;
|
||||
fp = NULL;
|
||||
http_close (http, 1);
|
||||
http = NULL;
|
||||
|
||||
leave:
|
||||
es_fclose (fp);
|
||||
http_close (http, 0);
|
||||
xfree (request_buffer);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
static gpg_error_t
|
||||
armor_data (char **r_string, const void *data, size_t datalen)
|
||||
{
|
||||
gpg_error_t err;
|
||||
struct b64state b64state;
|
||||
estream_t fp;
|
||||
long length;
|
||||
char *buffer;
|
||||
size_t nread;
|
||||
|
||||
*r_string = NULL;
|
||||
|
||||
fp = es_fopenmem (0, "rw");
|
||||
if (!fp)
|
||||
return gpg_error_from_syserror ();
|
||||
|
||||
if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK"))
|
||||
|| (err=b64enc_write (&b64state, data, datalen))
|
||||
|| (err = b64enc_finish (&b64state)))
|
||||
{
|
||||
es_fclose (fp);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* FIXME: To avoid the extra buffer allocation estream should
|
||||
provide a function to snatch the internal allocated memory from
|
||||
such a memory stream. */
|
||||
length = es_ftell (fp);
|
||||
if (length < 0)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
es_fclose (fp);
|
||||
return err;
|
||||
}
|
||||
|
||||
buffer = xtrymalloc (length+1);
|
||||
if (!buffer)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
es_fclose (fp);
|
||||
return err;
|
||||
}
|
||||
|
||||
es_rewind (fp);
|
||||
if (es_read (fp, buffer, length, &nread))
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
es_fclose (fp);
|
||||
return err;
|
||||
}
|
||||
buffer[nread] = 0;
|
||||
es_fclose (fp);
|
||||
|
||||
*r_string = buffer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Search the keyserver identified by URI for keys matching PATTERN.
|
||||
On success R_FP has an open stream to read the data. */
|
||||
gpg_error_t
|
||||
@ -251,7 +318,7 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
|
||||
}
|
||||
|
||||
/* Send the request. */
|
||||
err = send_request (ctrl, request, hostport, &fp);
|
||||
err = send_request (ctrl, request, hostport, NULL, NULL, &fp);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
@ -368,7 +435,7 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
|
||||
}
|
||||
|
||||
/* Send the request. */
|
||||
err = send_request (ctrl, request, hostport, &fp);
|
||||
err = send_request (ctrl, request, hostport, NULL, NULL, &fp);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
@ -384,3 +451,108 @@ ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Callback parameters for put_post_cb. */
|
||||
struct put_post_parm_s
|
||||
{
|
||||
char *datastring;
|
||||
};
|
||||
|
||||
|
||||
/* Helper for ks_hkp_put. */
|
||||
static gpg_error_t
|
||||
put_post_cb (void *opaque, http_t http)
|
||||
{
|
||||
struct put_post_parm_s *parm = opaque;
|
||||
gpg_error_t err = 0;
|
||||
estream_t fp;
|
||||
size_t len;
|
||||
|
||||
fp = http_get_write_ptr (http);
|
||||
len = strlen (parm->datastring);
|
||||
|
||||
es_fprintf (fp,
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */);
|
||||
http_start_data (http);
|
||||
if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL))
|
||||
err = gpg_error_from_syserror ();
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Send the key in {DATA,DATALEN} to the keyserver identified by URI. */
|
||||
gpg_error_t
|
||||
ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
|
||||
{
|
||||
gpg_error_t err;
|
||||
const char *scheme;
|
||||
char portstr[10];
|
||||
char *hostport = NULL;
|
||||
char *request = NULL;
|
||||
estream_t fp = NULL;
|
||||
struct put_post_parm_s parm;
|
||||
char *armored = NULL;
|
||||
|
||||
parm.datastring = NULL;
|
||||
|
||||
/* Map scheme and port. */
|
||||
if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https"))
|
||||
{
|
||||
scheme = "https";
|
||||
strcpy (portstr, "443");
|
||||
}
|
||||
else /* HKP or HTTP. */
|
||||
{
|
||||
scheme = "http";
|
||||
strcpy (portstr, "11371");
|
||||
}
|
||||
if (uri->port)
|
||||
snprintf (portstr, sizeof portstr, "%hu", uri->port);
|
||||
else
|
||||
{} /*fixme_do_srv_lookup ()*/
|
||||
|
||||
err = armor_data (&armored, data, datalen);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS);
|
||||
if (!parm.datastring)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
xfree (armored);
|
||||
armored = NULL;
|
||||
|
||||
/* Build the request string. */
|
||||
hostport = strconcat (scheme, "://",
|
||||
*uri->host? uri->host: "localhost",
|
||||
":", portstr, NULL);
|
||||
if (!hostport)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
|
||||
request = strconcat (hostport, "/pks/add", NULL);
|
||||
if (!request)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
|
||||
/* Send the request. */
|
||||
err = send_request (ctrl, request, hostport, put_post_cb, &parm, &fp);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
leave:
|
||||
es_fclose (fp);
|
||||
xfree (parm.datastring);
|
||||
xfree (armored);
|
||||
xfree (request);
|
||||
xfree (hostport);
|
||||
return err;
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ gpg_error_t ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
|
||||
estream_t *r_fp);
|
||||
gpg_error_t ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri,
|
||||
const char *keyspec, estream_t *r_fp);
|
||||
gpg_error_t ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri,
|
||||
const void *data, size_t datalen);
|
||||
|
||||
|
||||
|
||||
|
@ -47,6 +47,12 @@
|
||||
something reasonable. */
|
||||
#define MAX_CERT_LENGTH (8*1024)
|
||||
|
||||
/* The same goes for OpenPGP keyblocks, but here we need to allow for
|
||||
much longer blocks; a 200k keyblock is not too unusual for keys
|
||||
with a lot of signatures (e.g. 0x5b0358a2). */
|
||||
#define MAX_KEYBLOCK_LENGTH (512*1024)
|
||||
|
||||
|
||||
#define PARM_ERROR(t) assuan_set_error (ctx, \
|
||||
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
|
||||
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
|
||||
@ -1535,6 +1541,70 @@ cmd_ks_get (assuan_context_t ctx, char *line)
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const char hlp_ks_put[] =
|
||||
"KS_PUT\n"
|
||||
"\n"
|
||||
"Send a key to the configured OpenPGP keyservers. The actual key material\n"
|
||||
"is then requested by Dirmngr using\n"
|
||||
"\n"
|
||||
" INQUIRE KEYBLOCK\n"
|
||||
"\n"
|
||||
"The client shall respond with a binary version of the keyblock. For LDAP\n"
|
||||
"keyservers Dirmngr may ask for meta information of the provided keyblock\n"
|
||||
"using:\n"
|
||||
"\n"
|
||||
" INQUIRE KEYBLOCK_INFO\n"
|
||||
"\n"
|
||||
"The client shall respond with a colon delimited info lines";
|
||||
static gpg_error_t
|
||||
cmd_ks_put (assuan_context_t ctx, char *line)
|
||||
{
|
||||
ctrl_t ctrl = assuan_get_pointer (ctx);
|
||||
gpg_error_t err;
|
||||
unsigned char *value = NULL;
|
||||
size_t valuelen;
|
||||
unsigned char *info = NULL;
|
||||
size_t infolen;
|
||||
|
||||
/* No options for now. */
|
||||
line = skip_options (line);
|
||||
|
||||
/* Ask for the key material. */
|
||||
err = assuan_inquire (ctx, "KEYBLOCK",
|
||||
&value, &valuelen, MAX_KEYBLOCK_LENGTH);
|
||||
if (err)
|
||||
{
|
||||
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
||||
goto leave;
|
||||
}
|
||||
|
||||
if (!valuelen) /* No data returned; return a comprehensible error. */
|
||||
{
|
||||
err = gpg_error (GPG_ERR_MISSING_CERT);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
/* Ask for the key meta data. Not actually needed for HKP servers
|
||||
but we do it anyway test the client implementaion. */
|
||||
err = assuan_inquire (ctx, "KEYBLOCK_INFO",
|
||||
&info, &infolen, MAX_KEYBLOCK_LENGTH);
|
||||
if (err)
|
||||
{
|
||||
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
|
||||
goto leave;
|
||||
}
|
||||
|
||||
/* Send the key. */
|
||||
err = ks_action_put (ctrl, value, valuelen);
|
||||
|
||||
leave:
|
||||
xfree (info);
|
||||
xfree (value);
|
||||
return leave_cmd (ctx, err);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static const char hlp_getinfo[] =
|
||||
@ -1672,6 +1742,7 @@ register_commands (assuan_context_t ctx)
|
||||
{ "KEYSERVER", cmd_keyserver, hlp_keyserver },
|
||||
{ "KS_SEARCH", cmd_ks_search, hlp_ks_search },
|
||||
{ "KS_GET", cmd_ks_get, hlp_ks_get },
|
||||
{ "KS_PUT", cmd_ks_put, hlp_ks_put },
|
||||
{ "GETINFO", cmd_getinfo, hlp_getinfo },
|
||||
{ "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
|
||||
{ "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr },
|
||||
|
@ -1,13 +1,12 @@
|
||||
2011-01-18 Werner Koch <wk@g10code.com>
|
||||
2011-01-20 Werner Koch <wk@g10code.com>
|
||||
|
||||
* keyserver.c: Rewrite most stuff for use with dirmngr. Get rid
|
||||
of all spawn code. Work work pending.
|
||||
|
||||
* export.c (export_pubkeys_buffer): New.
|
||||
|
||||
* import.c (import_keys_es_stream): New.
|
||||
|
||||
2011-01-14 Werner Koch <wk@g10code.com>
|
||||
|
||||
* keyserver.c (parse_keyrec): Use trim_trailing_ws.
|
||||
|
||||
2011-01-07 Werner Koch <wk@g10code.com>
|
||||
|
||||
* call-dirmngr.c, call-dirmngr.h: New.
|
||||
* gpg.h (server_control_s): Add DIRMNGR_LOCAL.
|
||||
* gpg.c: Include call-dirmngr.h.
|
||||
@ -11671,7 +11670,7 @@ Thu Feb 12 22:24:42 1998 Werner Koch (wk@frodo)
|
||||
|
||||
|
||||
Copyright 1998,1999,2000,2001,2002,2003,2004,2005,
|
||||
2006,2007,2008,2009,2010 Free Software Foundation, Inc.
|
||||
2006,2007,2008,2009,2010,2011 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
|
||||
|
@ -59,6 +59,16 @@ struct ks_get_parm_s
|
||||
};
|
||||
|
||||
|
||||
/* Parameter structure used with the KS_PUT command. */
|
||||
struct ks_put_parm_s
|
||||
{
|
||||
assuan_context_t ctx;
|
||||
kbnode_t keyblock; /* The optional keyblock. */
|
||||
const void *data; /* The key in OpenPGP binary format. */
|
||||
size_t datalen; /* The length of DATA. */
|
||||
};
|
||||
|
||||
|
||||
/* Data used to associate an session with dirmngr contexts. We can't
|
||||
use a simple one to one mapping because we sometimes need two
|
||||
connection s to the dirmngr; for example while doing a listing and
|
||||
@ -436,3 +446,166 @@ gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, estream_t *r_fp)
|
||||
close_context (ctrl, ctx);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Handle the KS_PUT inquiries. */
|
||||
static gpg_error_t
|
||||
ks_put_inq_cb (void *opaque, const char *line)
|
||||
{
|
||||
struct ks_put_parm_s *parm = opaque;
|
||||
gpg_error_t err = 0;
|
||||
|
||||
if (!strncmp (line, "KEYBLOCK", 8) && (line[8] == ' ' || !line[8]))
|
||||
{
|
||||
if (parm->data)
|
||||
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
|
||||
}
|
||||
else if (!strncmp (line, "KEYBLOCK_INFO", 13) && (line[13]==' ' || !line[13]))
|
||||
{
|
||||
kbnode_t node;
|
||||
estream_t fp;
|
||||
|
||||
/* Parse the keyblock and send info lines back to the server. */
|
||||
fp = es_fopenmem (0, "rw");
|
||||
if (!fp)
|
||||
err = gpg_error_from_syserror ();
|
||||
|
||||
for (node = parm->keyblock; !err && node; node=node->next)
|
||||
{
|
||||
switch(node->pkt->pkttype)
|
||||
{
|
||||
case PKT_PUBLIC_KEY:
|
||||
case PKT_PUBLIC_SUBKEY:
|
||||
{
|
||||
PKT_public_key *pk = node->pkt->pkt.public_key;
|
||||
|
||||
keyid_from_pk (pk, NULL);
|
||||
|
||||
es_fprintf (fp, "%s:%08lX%08lX:%u:%u:%u:%u:%s%s:\n",
|
||||
node->pkt->pkttype==PKT_PUBLIC_KEY? "pub" : "sub",
|
||||
(ulong)pk->keyid[0], (ulong)pk->keyid[1],
|
||||
pk->pubkey_algo,
|
||||
nbits_from_pk (pk),
|
||||
pk->timestamp,
|
||||
pk->expiredate,
|
||||
pk->flags.revoked? "r":"",
|
||||
pk->has_expired? "e":"");
|
||||
}
|
||||
break;
|
||||
|
||||
case PKT_USER_ID:
|
||||
{
|
||||
PKT_user_id *uid = node->pkt->pkt.user_id;
|
||||
int r;
|
||||
|
||||
if (!uid->attrib_data)
|
||||
{
|
||||
es_fprintf (fp, "uid:");
|
||||
|
||||
/* Quote ':', '%', and any 8-bit characters. */
|
||||
for (r=0; r < uid->len; r++)
|
||||
{
|
||||
if (uid->name[r] == ':'
|
||||
|| uid->name[r]== '%'
|
||||
|| (uid->name[r]&0x80))
|
||||
es_fprintf (fp, "%%%02X", (byte)uid->name[r]);
|
||||
else
|
||||
es_putc (uid->name[r], fp);
|
||||
}
|
||||
|
||||
es_fprintf (fp, ":%u:%u:%s%s:\n",
|
||||
uid->created,uid->expiredate,
|
||||
uid->is_revoked? "r":"",
|
||||
uid->is_expired? "e":"");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
/* This bit is really for the benefit of people who
|
||||
store their keys in LDAP servers. It makes it easy
|
||||
to do queries for things like "all keys signed by
|
||||
Isabella". */
|
||||
case PKT_SIGNATURE:
|
||||
{
|
||||
PKT_signature *sig = node->pkt->pkt.signature;
|
||||
|
||||
if (IS_UID_SIG (sig))
|
||||
{
|
||||
es_fprintf (fp, "sig:%08lX%08lX:%X:%u:%u:\n",
|
||||
(ulong)sig->keyid[0],(ulong)sig->keyid[1],
|
||||
sig->sig_class, sig->timestamp,
|
||||
sig->expiredate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
/* Given that the last operation was an es_fprintf we should
|
||||
get the correct ERRNO if ferror indicates an error. */
|
||||
if (es_ferror (fp))
|
||||
err = gpg_error_from_syserror ();
|
||||
}
|
||||
|
||||
/* Without an error and if we have an keyblock at all, send the
|
||||
data back. */
|
||||
if (!err && parm->keyblock)
|
||||
{
|
||||
int rc;
|
||||
char buffer[512];
|
||||
size_t nread;
|
||||
|
||||
es_rewind (fp);
|
||||
while (!(rc=es_read (fp, buffer, sizeof buffer, &nread)) && nread)
|
||||
{
|
||||
err = assuan_send_data (parm->ctx, buffer, nread);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
if (!err && rc)
|
||||
err = gpg_error_from_syserror ();
|
||||
}
|
||||
es_fclose (fp);
|
||||
}
|
||||
else
|
||||
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/* Send a key to the configured server. {DATA,DATLEN} contains the
|
||||
key in OpenPGP binary transport format. If KEYBLOCK is not NULL it
|
||||
has the internal representaion of that key; this is for example
|
||||
used to convey meta data to LDAP keyservers. */
|
||||
gpg_error_t
|
||||
gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock)
|
||||
{
|
||||
gpg_error_t err;
|
||||
assuan_context_t ctx;
|
||||
struct ks_put_parm_s parm;
|
||||
|
||||
memset (&parm, 0, sizeof parm);
|
||||
|
||||
/* We are going to parse the keyblock, thus we better make sure the
|
||||
all information is readily available. */
|
||||
if (keyblock)
|
||||
merge_keys_and_selfsig (keyblock);
|
||||
|
||||
err = open_context (ctrl, &ctx);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
parm.ctx = ctx;
|
||||
parm.keyblock = keyblock;
|
||||
parm.data = data;
|
||||
parm.datalen = datalen;
|
||||
|
||||
err = assuan_transact (ctx, "KS_PUT", NULL, NULL,
|
||||
ks_put_inq_cb, &parm, NULL, NULL);
|
||||
|
||||
close_context (ctrl, ctx);
|
||||
return err;
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ gpg_error_t gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr,
|
||||
gpg_error_t (*cb)(void*, char *),
|
||||
void *cb_value);
|
||||
gpg_error_t gpg_dirmngr_ks_get (ctrl_t ctrl, char *pattern[], estream_t *r_fp);
|
||||
gpg_error_t gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen,
|
||||
kbnode_t keyblock);
|
||||
|
||||
|
||||
#endif /*GNUPG_G10_CALL_DIRMNGR_H*/
|
||||
|
54
g10/export.c
54
g10/export.c
@ -114,6 +114,60 @@ export_pubkeys_stream (ctrl_t ctrl, iobuf_t out, strlist_t users,
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Export a single key into a memory buffer.
|
||||
*/
|
||||
gpg_error_t
|
||||
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
|
||||
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
|
||||
{
|
||||
gpg_error_t err;
|
||||
iobuf_t iobuf;
|
||||
int any;
|
||||
strlist_t helplist;
|
||||
|
||||
*r_keyblock = NULL;
|
||||
*r_data = NULL;
|
||||
*r_datalen = 0;
|
||||
|
||||
helplist = NULL;
|
||||
if (!add_to_strlist_try (&helplist, keyspec))
|
||||
return gpg_error_from_syserror ();
|
||||
|
||||
iobuf = iobuf_temp ();
|
||||
err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options, &any);
|
||||
if (!err && !any)
|
||||
err = gpg_error (GPG_ERR_NOT_FOUND);
|
||||
if (!err)
|
||||
{
|
||||
const void *src;
|
||||
size_t datalen;
|
||||
|
||||
iobuf_flush_temp (iobuf);
|
||||
src = iobuf_get_temp_buffer (iobuf);
|
||||
datalen = iobuf_get_temp_length (iobuf);
|
||||
if (!datalen)
|
||||
err = gpg_error (GPG_ERR_NO_PUBKEY);
|
||||
else if (!(*r_data = xtrymalloc (datalen)))
|
||||
err = gpg_error_from_syserror ();
|
||||
else
|
||||
{
|
||||
memcpy (*r_data, src, datalen);
|
||||
*r_datalen = datalen;
|
||||
}
|
||||
}
|
||||
iobuf_close (iobuf);
|
||||
free_strlist (helplist);
|
||||
if (err && *r_keyblock)
|
||||
{
|
||||
release_kbnode (*r_keyblock);
|
||||
*r_keyblock = NULL;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
export_seckeys (ctrl_t ctrl, strlist_t users )
|
||||
{
|
||||
|
748
g10/keyserver.c
748
g10/keyserver.c
@ -111,13 +111,11 @@ static struct parse_options keyserver_opts[]=
|
||||
{NULL,0,NULL,NULL}
|
||||
};
|
||||
|
||||
static int keyserver_work (ctrl_t ctrl, enum ks_action action,strlist_t list,
|
||||
KEYDB_SEARCH_DESC *desc,int count,
|
||||
unsigned char **fpr,size_t *fpr_len,
|
||||
struct keyserver_spec *keyserver);
|
||||
static gpg_error_t keyserver_get (ctrl_t ctrl,
|
||||
KEYDB_SEARCH_DESC *desc, int ndesc,
|
||||
struct keyserver_spec *keyserver);
|
||||
static gpg_error_t keyserver_put (ctrl_t ctrl, strlist_t keyspecs,
|
||||
struct keyserver_spec *keyserver);
|
||||
|
||||
|
||||
/* Reasonable guess */
|
||||
@ -989,664 +987,6 @@ search_line_handler (void *opaque, char *line)
|
||||
}
|
||||
|
||||
|
||||
/* We sometimes want to use a different gpgkeys_xxx for a given
|
||||
protocol (for example, ldaps is handled by gpgkeys_ldap). Map
|
||||
these here. */
|
||||
static const char *
|
||||
keyserver_typemap(const char *type)
|
||||
{
|
||||
if(strcmp(type,"ldaps")==0)
|
||||
return "ldap";
|
||||
else if(strcmp(type,"hkps")==0)
|
||||
return "hkp";
|
||||
else
|
||||
return type;
|
||||
}
|
||||
|
||||
/* The PGP LDAP and the curl fetch-a-LDAP-object methodologies are
|
||||
sufficiently different that we can't use curl to do LDAP. */
|
||||
static int
|
||||
direct_uri_map(const char *scheme,unsigned int is_direct)
|
||||
{
|
||||
if(is_direct && strcmp(scheme,"ldap")==0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if GNUPG_MAJOR_VERSION == 2
|
||||
#define GPGKEYS_PREFIX "gpg2keys_"
|
||||
#else
|
||||
#define GPGKEYS_PREFIX "gpgkeys_"
|
||||
#endif
|
||||
#define GPGKEYS_CURL GPGKEYS_PREFIX "curl" EXEEXT
|
||||
#define GPGKEYS_PREFIX_LEN (strlen(GPGKEYS_CURL))
|
||||
#define KEYSERVER_ARGS_KEEP " -o \"%O\" \"%I\""
|
||||
#define KEYSERVER_ARGS_NOKEEP " -o \"%o\" \"%i\""
|
||||
|
||||
static int
|
||||
keyserver_spawn (ctrl_t ctrl,
|
||||
enum ks_action action,strlist_t list,KEYDB_SEARCH_DESC *desc,
|
||||
int count,int *prog,unsigned char **fpr,size_t *fpr_len,
|
||||
struct keyserver_spec *keyserver)
|
||||
{
|
||||
int ret=0,i,gotversion=0,outofband=0;
|
||||
strlist_t temp;
|
||||
unsigned int maxlen,buflen;
|
||||
char *command,*end,*searchstr=NULL;
|
||||
byte *line=NULL;
|
||||
struct exec_info *spawn;
|
||||
const char *scheme;
|
||||
const char *libexecdir = gnupg_libexecdir ();
|
||||
|
||||
assert(keyserver);
|
||||
|
||||
#ifdef EXEC_TEMPFILE_ONLY
|
||||
opt.keyserver_options.options|=KEYSERVER_USE_TEMP_FILES;
|
||||
#endif
|
||||
|
||||
/* Build the filename for the helper to execute */
|
||||
scheme=keyserver_typemap(keyserver->scheme);
|
||||
|
||||
#ifdef DISABLE_KEYSERVER_PATH
|
||||
/* Destroy any path we might have. This is a little tricky,
|
||||
portability-wise. It's not correct to delete the PATH
|
||||
environment variable, as that may fall back to a system built-in
|
||||
PATH. Similarly, it is not correct to set PATH to the null
|
||||
string (PATH="") since this actually deletes the PATH environment
|
||||
variable under MinGW. The safest thing to do here is to force
|
||||
PATH to be GNUPG_LIBEXECDIR. All this is not that meaningful on
|
||||
Unix-like systems (since we're going to give a full path to
|
||||
gpgkeys_foo), but on W32 it prevents loading any DLLs from
|
||||
directories in %PATH%.
|
||||
|
||||
After some more thinking about this we came to the conclusion
|
||||
that it is better to load the helpers from the directory where
|
||||
the program of this process lives. Fortunately Windows provides
|
||||
a way to retrieve this and our gnupg_libexecdir function has been
|
||||
modified to return just this. Setting the exec-path is not
|
||||
anymore required.
|
||||
set_exec_path(libexecdir);
|
||||
*/
|
||||
#else
|
||||
if(opt.exec_path_set)
|
||||
{
|
||||
/* If exec-path was set, and DISABLE_KEYSERVER_PATH is
|
||||
undefined, then don't specify a full path to gpgkeys_foo, so
|
||||
that the PATH can work. */
|
||||
command=xmalloc(GPGKEYS_PREFIX_LEN+strlen(scheme)+3+strlen(EXEEXT)+1);
|
||||
command[0]='\0';
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* Specify a full path to gpgkeys_foo. */
|
||||
command=xmalloc(strlen(libexecdir)+strlen(DIRSEP_S)+
|
||||
GPGKEYS_PREFIX_LEN+strlen(scheme)+3+strlen(EXEEXT)+1);
|
||||
strcpy(command,libexecdir);
|
||||
strcat(command,DIRSEP_S);
|
||||
}
|
||||
|
||||
end=command+strlen(command);
|
||||
|
||||
/* Build a path for the keyserver helper. If it is direct_uri
|
||||
(i.e. an object fetch and not a keyserver), then add "_uri" to
|
||||
the end to distinguish the keyserver helper from an object
|
||||
fetcher that can speak that protocol (this is a problem for
|
||||
LDAP). */
|
||||
|
||||
strcat(command,GPGKEYS_PREFIX);
|
||||
strcat(command,scheme);
|
||||
|
||||
/* This "_uri" thing is in case we need to call a direct handler
|
||||
instead of the keyserver handler. This lets us use gpgkeys_curl
|
||||
or gpgkeys_ldap_uri (we don't provide it, but a user might)
|
||||
instead of gpgkeys_ldap to fetch things like
|
||||
ldap://keyserver.pgp.com/o=PGP%20keys?pgpkey?sub?pgpkeyid=99242560 */
|
||||
|
||||
if(direct_uri_map(scheme,keyserver->flags.direct_uri))
|
||||
strcat(command,"_uri");
|
||||
|
||||
strcat(command,EXEEXT);
|
||||
|
||||
/* Can we execute it? If not, try curl as our catchall. */
|
||||
if(path_access(command,X_OK)!=0)
|
||||
strcpy(end,GPGKEYS_CURL);
|
||||
|
||||
if(opt.keyserver_options.options&KEYSERVER_USE_TEMP_FILES)
|
||||
{
|
||||
if(opt.keyserver_options.options&KEYSERVER_KEEP_TEMP_FILES)
|
||||
{
|
||||
command=xrealloc(command,strlen(command)+
|
||||
strlen(KEYSERVER_ARGS_KEEP)+1);
|
||||
strcat(command,KEYSERVER_ARGS_KEEP);
|
||||
}
|
||||
else
|
||||
{
|
||||
command=xrealloc(command,strlen(command)+
|
||||
strlen(KEYSERVER_ARGS_NOKEEP)+1);
|
||||
strcat(command,KEYSERVER_ARGS_NOKEEP);
|
||||
}
|
||||
|
||||
ret=exec_write(&spawn,NULL,command,NULL,0,0);
|
||||
}
|
||||
else
|
||||
ret=exec_write(&spawn,command,NULL,NULL,0,0);
|
||||
|
||||
xfree(command);
|
||||
|
||||
if(ret)
|
||||
return ret;
|
||||
|
||||
fprintf(spawn->tochild,
|
||||
"# This is a GnuPG %s keyserver communications file\n",VERSION);
|
||||
fprintf(spawn->tochild,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
|
||||
fprintf(spawn->tochild,"PROGRAM %s\n",VERSION);
|
||||
fprintf(spawn->tochild,"SCHEME %s\n",keyserver->scheme);
|
||||
|
||||
if(keyserver->opaque)
|
||||
fprintf(spawn->tochild,"OPAQUE %s\n",keyserver->opaque);
|
||||
else
|
||||
{
|
||||
if(keyserver->auth)
|
||||
fprintf(spawn->tochild,"AUTH %s\n",keyserver->auth);
|
||||
|
||||
if(keyserver->host)
|
||||
fprintf(spawn->tochild,"HOST %s\n",keyserver->host);
|
||||
|
||||
if(keyserver->port)
|
||||
fprintf(spawn->tochild,"PORT %s\n",keyserver->port);
|
||||
|
||||
if(keyserver->path)
|
||||
fprintf(spawn->tochild,"PATH %s\n",keyserver->path);
|
||||
}
|
||||
|
||||
/* Write global options */
|
||||
|
||||
for(temp=opt.keyserver_options.other;temp;temp=temp->next)
|
||||
fprintf(spawn->tochild,"OPTION %s\n",temp->d);
|
||||
|
||||
/* Write per-keyserver options */
|
||||
|
||||
for(temp=keyserver->options;temp;temp=temp->next)
|
||||
fprintf(spawn->tochild,"OPTION %s\n",temp->d);
|
||||
|
||||
switch(action)
|
||||
{
|
||||
case KS_GET:
|
||||
{
|
||||
fprintf(spawn->tochild,"COMMAND GET\n\n");
|
||||
|
||||
/* Which keys do we want? */
|
||||
|
||||
for(i=0;i<count;i++)
|
||||
{
|
||||
int quiet=0;
|
||||
|
||||
if(desc[i].mode==KEYDB_SEARCH_MODE_FPR20)
|
||||
{
|
||||
int f;
|
||||
|
||||
fprintf(spawn->tochild,"0x");
|
||||
|
||||
for(f=0;f<MAX_FINGERPRINT_LEN;f++)
|
||||
fprintf(spawn->tochild,"%02X",desc[i].u.fpr[f]);
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
}
|
||||
else if(desc[i].mode==KEYDB_SEARCH_MODE_FPR16)
|
||||
{
|
||||
int f;
|
||||
|
||||
fprintf(spawn->tochild,"0x");
|
||||
|
||||
for(f=0;f<16;f++)
|
||||
fprintf(spawn->tochild,"%02X",desc[i].u.fpr[f]);
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
}
|
||||
else if(desc[i].mode==KEYDB_SEARCH_MODE_LONG_KID)
|
||||
fprintf(spawn->tochild,"0x%08lX%08lX\n",
|
||||
(ulong)desc[i].u.kid[0],
|
||||
(ulong)desc[i].u.kid[1]);
|
||||
else if(desc[i].mode==KEYDB_SEARCH_MODE_SHORT_KID)
|
||||
fprintf(spawn->tochild,"0x%08lX\n",
|
||||
(ulong)desc[i].u.kid[1]);
|
||||
else if(desc[i].mode==KEYDB_SEARCH_MODE_EXACT)
|
||||
{
|
||||
fprintf(spawn->tochild,"0x0000000000000000\n");
|
||||
quiet=1;
|
||||
}
|
||||
else if(desc[i].mode==KEYDB_SEARCH_MODE_NONE)
|
||||
continue;
|
||||
else
|
||||
BUG();
|
||||
|
||||
if(!quiet)
|
||||
{
|
||||
if(keyserver->host)
|
||||
log_info(_("requesting key %s from %s server %s\n"),
|
||||
keystr_from_desc(&desc[i]),
|
||||
keyserver->scheme,keyserver->host);
|
||||
else
|
||||
log_info(_("requesting key %s from %s\n"),
|
||||
keystr_from_desc(&desc[i]),keyserver->uri);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KS_GETNAME:
|
||||
{
|
||||
strlist_t key;
|
||||
|
||||
fprintf(spawn->tochild,"COMMAND GETNAME\n\n");
|
||||
|
||||
/* Which names do we want? */
|
||||
|
||||
for(key=list;key!=NULL;key=key->next)
|
||||
fprintf(spawn->tochild,"%s\n",key->d);
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
|
||||
if(keyserver->host)
|
||||
log_info(_("searching for names from %s server %s\n"),
|
||||
keyserver->scheme,keyserver->host);
|
||||
else
|
||||
log_info(_("searching for names from %s\n"),keyserver->uri);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KS_SEND:
|
||||
{
|
||||
strlist_t key;
|
||||
|
||||
/* Note the extra \n here to send an empty keylist block */
|
||||
fprintf(spawn->tochild,"COMMAND SEND\n\n\n");
|
||||
|
||||
for(key=list;key!=NULL;key=key->next)
|
||||
{
|
||||
armor_filter_context_t *afx;
|
||||
IOBUF buffer = iobuf_temp ();
|
||||
KBNODE block;
|
||||
|
||||
temp=NULL;
|
||||
add_to_strlist(&temp,key->d);
|
||||
|
||||
afx = new_armor_context ();
|
||||
afx->what = 1;
|
||||
/* Tell the armor filter to use Unix-style \n line
|
||||
endings, since we're going to fprintf this to a file
|
||||
that (on Win32) is open in text mode. The win32 stdio
|
||||
will transform the \n to \r\n and we'll end up with the
|
||||
proper line endings on win32. This is a no-op on
|
||||
Unix. */
|
||||
afx->eol[0] = '\n';
|
||||
push_armor_filter (afx, buffer);
|
||||
release_armor_context (afx);
|
||||
|
||||
/* TODO: Remove Comment: lines from keys exported this
|
||||
way? */
|
||||
|
||||
if(export_pubkeys_stream (ctrl, buffer,temp,&block,
|
||||
opt.keyserver_options.export_options)==-1)
|
||||
iobuf_close(buffer);
|
||||
else
|
||||
{
|
||||
KBNODE node;
|
||||
|
||||
iobuf_flush_temp(buffer);
|
||||
|
||||
merge_keys_and_selfsig(block);
|
||||
|
||||
fprintf(spawn->tochild,"INFO %08lX%08lX BEGIN\n",
|
||||
(ulong)block->pkt->pkt.public_key->keyid[0],
|
||||
(ulong)block->pkt->pkt.public_key->keyid[1]);
|
||||
|
||||
for(node=block;node;node=node->next)
|
||||
{
|
||||
switch(node->pkt->pkttype)
|
||||
{
|
||||
default:
|
||||
continue;
|
||||
|
||||
case PKT_PUBLIC_KEY:
|
||||
case PKT_PUBLIC_SUBKEY:
|
||||
{
|
||||
PKT_public_key *pk=node->pkt->pkt.public_key;
|
||||
|
||||
keyid_from_pk(pk,NULL);
|
||||
|
||||
fprintf(spawn->tochild,"%sb:%08lX%08lX:%u:%u:%u:%u:",
|
||||
node->pkt->pkttype==PKT_PUBLIC_KEY?"pu":"su",
|
||||
(ulong)pk->keyid[0],(ulong)pk->keyid[1],
|
||||
pk->pubkey_algo,
|
||||
nbits_from_pk(pk),
|
||||
pk->timestamp,
|
||||
pk->expiredate);
|
||||
|
||||
if(pk->flags.revoked)
|
||||
fprintf(spawn->tochild,"r");
|
||||
if(pk->has_expired)
|
||||
fprintf(spawn->tochild,"e");
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case PKT_USER_ID:
|
||||
{
|
||||
PKT_user_id *uid=node->pkt->pkt.user_id;
|
||||
int r;
|
||||
|
||||
if(uid->attrib_data)
|
||||
continue;
|
||||
|
||||
fprintf(spawn->tochild,"uid:");
|
||||
|
||||
/* Quote ':', '%', and any 8-bit
|
||||
characters */
|
||||
for(r=0;r<uid->len;r++)
|
||||
{
|
||||
if(uid->name[r]==':' || uid->name[r]=='%'
|
||||
|| uid->name[r]&0x80)
|
||||
fprintf(spawn->tochild,"%%%02X",
|
||||
(byte)uid->name[r]);
|
||||
else
|
||||
fprintf(spawn->tochild,"%c",uid->name[r]);
|
||||
}
|
||||
|
||||
fprintf(spawn->tochild,":%u:%u:",
|
||||
uid->created,uid->expiredate);
|
||||
|
||||
if(uid->is_revoked)
|
||||
fprintf(spawn->tochild,"r");
|
||||
if(uid->is_expired)
|
||||
fprintf(spawn->tochild,"e");
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
}
|
||||
break;
|
||||
|
||||
/* This bit is really for the benefit of
|
||||
people who store their keys in LDAP
|
||||
servers. It makes it easy to do queries
|
||||
for things like "all keys signed by
|
||||
Isabella". */
|
||||
case PKT_SIGNATURE:
|
||||
{
|
||||
PKT_signature *sig=node->pkt->pkt.signature;
|
||||
|
||||
if(!IS_UID_SIG(sig))
|
||||
continue;
|
||||
|
||||
fprintf(spawn->tochild,"sig:%08lX%08lX:%X:%u:%u\n",
|
||||
(ulong)sig->keyid[0],(ulong)sig->keyid[1],
|
||||
sig->sig_class,sig->timestamp,
|
||||
sig->expiredate);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(spawn->tochild,"INFO %08lX%08lX END\n",
|
||||
(ulong)block->pkt->pkt.public_key->keyid[0],
|
||||
(ulong)block->pkt->pkt.public_key->keyid[1]);
|
||||
|
||||
fprintf(spawn->tochild,"KEY %08lX%08lX BEGIN\n",
|
||||
(ulong)block->pkt->pkt.public_key->keyid[0],
|
||||
(ulong)block->pkt->pkt.public_key->keyid[1]);
|
||||
fwrite(iobuf_get_temp_buffer(buffer),
|
||||
iobuf_get_temp_length(buffer),1,spawn->tochild);
|
||||
fprintf(spawn->tochild,"KEY %08lX%08lX END\n",
|
||||
(ulong)block->pkt->pkt.public_key->keyid[0],
|
||||
(ulong)block->pkt->pkt.public_key->keyid[1]);
|
||||
|
||||
iobuf_close(buffer);
|
||||
|
||||
if(keyserver->host)
|
||||
log_info(_("sending key %s to %s server %s\n"),
|
||||
keystr(block->pkt->pkt.public_key->keyid),
|
||||
keyserver->scheme,keyserver->host);
|
||||
else
|
||||
log_info(_("sending key %s to %s\n"),
|
||||
keystr(block->pkt->pkt.public_key->keyid),
|
||||
keyserver->uri);
|
||||
|
||||
release_kbnode(block);
|
||||
}
|
||||
|
||||
free_strlist(temp);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case KS_SEARCH:
|
||||
{
|
||||
strlist_t key;
|
||||
|
||||
fprintf(spawn->tochild,"COMMAND SEARCH\n\n");
|
||||
|
||||
/* Which keys do we want? Remember that the gpgkeys_ program
|
||||
is going to lump these together into a search string. */
|
||||
|
||||
for(key=list;key!=NULL;key=key->next)
|
||||
{
|
||||
fprintf(spawn->tochild,"%s\n",key->d);
|
||||
if(key!=list)
|
||||
{
|
||||
searchstr=xrealloc(searchstr,
|
||||
strlen(searchstr)+strlen(key->d)+2);
|
||||
strcat(searchstr," ");
|
||||
}
|
||||
else
|
||||
{
|
||||
searchstr=xmalloc(strlen(key->d)+1);
|
||||
searchstr[0]='\0';
|
||||
}
|
||||
|
||||
strcat(searchstr,key->d);
|
||||
}
|
||||
|
||||
fprintf(spawn->tochild,"\n");
|
||||
|
||||
if(keyserver->host)
|
||||
log_info(_("searching for \"%s\" from %s server %s\n"),
|
||||
searchstr,keyserver->scheme,keyserver->host);
|
||||
else
|
||||
log_info(_("searching for \"%s\" from %s\n"),
|
||||
searchstr,keyserver->uri);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
log_fatal(_("no keyserver action!\n"));
|
||||
break;
|
||||
}
|
||||
|
||||
/* Done sending, so start reading. */
|
||||
ret=exec_read(spawn);
|
||||
if(ret)
|
||||
goto fail;
|
||||
|
||||
/* Now handle the response */
|
||||
|
||||
for(;;)
|
||||
{
|
||||
int plen;
|
||||
char *ptr;
|
||||
|
||||
maxlen=1024;
|
||||
if(iobuf_read_line(spawn->fromchild,&line,&buflen,&maxlen)==0)
|
||||
{
|
||||
ret = gpg_error_from_syserror ();
|
||||
goto fail; /* i.e. EOF */
|
||||
}
|
||||
|
||||
ptr=line;
|
||||
|
||||
/* remove trailing whitespace */
|
||||
plen=strlen(ptr);
|
||||
while(plen>0 && ascii_isspace(ptr[plen-1]))
|
||||
plen--;
|
||||
plen[ptr]='\0';
|
||||
|
||||
if(*ptr=='\0')
|
||||
break;
|
||||
|
||||
if(ascii_strncasecmp(ptr,"VERSION ",8)==0)
|
||||
{
|
||||
gotversion=1;
|
||||
|
||||
if(atoi(&ptr[8])!=KEYSERVER_PROTO_VERSION)
|
||||
{
|
||||
log_error(_("invalid keyserver protocol (us %d!=handler %d)\n"),
|
||||
KEYSERVER_PROTO_VERSION,atoi(&ptr[8]));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else if(ascii_strncasecmp(ptr,"PROGRAM ",8)==0)
|
||||
{
|
||||
if(ascii_strncasecmp(&ptr[8],VERSION,strlen(VERSION))!=0)
|
||||
log_info(_("WARNING: keyserver handler from a different"
|
||||
" version of GnuPG (%s)\n"),&ptr[8]);
|
||||
}
|
||||
else if(ascii_strncasecmp(ptr,"OPTION OUTOFBAND",16)==0)
|
||||
outofband=1; /* Currently the only OPTION */
|
||||
}
|
||||
|
||||
if(!gotversion)
|
||||
{
|
||||
log_error(_("keyserver did not send VERSION\n"));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(!outofband)
|
||||
switch(action)
|
||||
{
|
||||
case KS_GET:
|
||||
case KS_GETNAME:
|
||||
{
|
||||
void *stats_handle;
|
||||
|
||||
stats_handle=import_new_stats_handle();
|
||||
|
||||
/* Slurp up all the key data. In the future, it might be
|
||||
nice to look for KEY foo OUTOFBAND and FAILED indicators.
|
||||
It's harmless to ignore them, but ignoring them does make
|
||||
gpg complain about "no valid OpenPGP data found". One
|
||||
way to do this could be to continue parsing this
|
||||
line-by-line and make a temp iobuf for each key. */
|
||||
|
||||
/* FIXME: Pass CTRL. */
|
||||
import_keys_stream (NULL, spawn->fromchild,stats_handle,fpr,fpr_len,
|
||||
opt.keyserver_options.import_options);
|
||||
|
||||
import_print_stats(stats_handle);
|
||||
import_release_stats_handle(stats_handle);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Nothing to do here */
|
||||
case KS_SEND:
|
||||
break;
|
||||
|
||||
case KS_SEARCH:
|
||||
//keyserver_search_prompt (ctrl, spawn->fromchild,searchstr);
|
||||
break;
|
||||
|
||||
default:
|
||||
log_fatal(_("no keyserver action!\n"));
|
||||
break;
|
||||
}
|
||||
|
||||
fail:
|
||||
xfree(line);
|
||||
xfree(searchstr);
|
||||
|
||||
|
||||
*prog=exec_finish(spawn);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static int
|
||||
keyserver_work (ctrl_t ctrl,
|
||||
enum ks_action action,strlist_t list,KEYDB_SEARCH_DESC *desc,
|
||||
int count,unsigned char **fpr,size_t *fpr_len,
|
||||
struct keyserver_spec *keyserver)
|
||||
{
|
||||
int rc=0,ret=0;
|
||||
|
||||
if (!keyserver)
|
||||
{
|
||||
log_error (_("no keyserver known (use option --keyserver)\n"));
|
||||
return gpg_error (GPG_ERR_BAD_URI);
|
||||
}
|
||||
|
||||
|
||||
rc = keyserver_spawn (ctrl, action, list, desc, count,
|
||||
&ret, fpr, fpr_len, keyserver);
|
||||
if (ret)
|
||||
{
|
||||
switch(ret)
|
||||
{
|
||||
case KEYSERVER_SCHEME_NOT_FOUND:
|
||||
log_error(_("no handler for keyserver scheme `%s'\n"),
|
||||
keyserver->scheme);
|
||||
break;
|
||||
|
||||
case KEYSERVER_NOT_SUPPORTED:
|
||||
log_error(_("action `%s' not supported with keyserver "
|
||||
"scheme `%s'\n"),
|
||||
action==KS_GET?"get":action==KS_SEND?"send":
|
||||
action==KS_SEARCH?"search":"unknown",
|
||||
keyserver->scheme);
|
||||
break;
|
||||
|
||||
case KEYSERVER_VERSION_ERROR:
|
||||
log_error(_(GPGKEYS_PREFIX "%s does not support"
|
||||
" handler version %d\n"),
|
||||
keyserver_typemap(keyserver->scheme),
|
||||
KEYSERVER_PROTO_VERSION);
|
||||
break;
|
||||
|
||||
case KEYSERVER_TIMEOUT:
|
||||
log_error(_("keyserver timed out\n"));
|
||||
break;
|
||||
|
||||
case KEYSERVER_INTERNAL_ERROR:
|
||||
default:
|
||||
log_error(_("keyserver internal error\n"));
|
||||
break;
|
||||
}
|
||||
|
||||
return G10ERR_KEYSERVER;
|
||||
}
|
||||
|
||||
if (rc)
|
||||
{
|
||||
log_error (_("keyserver communications error: %s\n"),g10_errstr(rc));
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int
|
||||
keyserver_export (ctrl_t ctrl, strlist_t users)
|
||||
@ -1674,7 +1014,7 @@ keyserver_export (ctrl_t ctrl, strlist_t users)
|
||||
|
||||
if(sl)
|
||||
{
|
||||
rc = keyserver_work (ctrl, KS_SEND,sl,NULL,0,NULL,NULL,opt.keyserver);
|
||||
rc = keyserver_put (ctrl, sl, opt.keyserver);
|
||||
free_strlist(sl);
|
||||
}
|
||||
|
||||
@ -2113,26 +1453,6 @@ keyserver_search (ctrl_t ctrl, strlist_t tokens)
|
||||
|
||||
/* Called using:
|
||||
|
||||
show_prompt:
|
||||
import:
|
||||
import_foo:
|
||||
refresh:
|
||||
rc=keyserver_work (ctrl, KS_GET, NULL, desc, count,
|
||||
NULL, NULL, opt.keyserver);
|
||||
|
||||
|
||||
fetch:
|
||||
rc = keyserver_work (ctrl, KS_GET, NULL, &desc, 1, NULL, NULL, spec);
|
||||
if(rc)
|
||||
log_info (_("WARNING: unable to fetch URI %s: %s\n"),
|
||||
sl->d,g10_errstr(rc));
|
||||
|
||||
|
||||
export:
|
||||
rc = keyserver_work (ctrl, KS_SEND,sl,NULL,0,NULL,NULL,opt.keyserver);
|
||||
|
||||
|
||||
|
||||
import_name:
|
||||
rc = keyserver_work (ctrl, KS_GETNAME, list, NULL,
|
||||
0, fpr, fpr_len, keyserver);
|
||||
@ -2267,6 +1587,58 @@ keyserver_get (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, int ndesc,
|
||||
}
|
||||
|
||||
|
||||
/* Send all keys specified by KEYSPECS to the KEYSERVERS. */
|
||||
static gpg_error_t
|
||||
keyserver_put (ctrl_t ctrl, strlist_t keyspecs,
|
||||
struct keyserver_spec *keyserver)
|
||||
|
||||
{
|
||||
gpg_error_t err;
|
||||
strlist_t kspec;
|
||||
|
||||
if (!keyspecs)
|
||||
return 0; /* Return success if the list is empty. */
|
||||
|
||||
if (!opt.keyserver)
|
||||
{
|
||||
log_error (_("no keyserver known (use option --keyserver)\n"));
|
||||
return gpg_error (GPG_ERR_NO_KEYSERVER);
|
||||
}
|
||||
|
||||
for (kspec = keyspecs; kspec; kspec = kspec->next)
|
||||
{
|
||||
void *data;
|
||||
size_t datalen;
|
||||
kbnode_t keyblock;
|
||||
|
||||
err = export_pubkey_buffer (ctrl, kspec->d,
|
||||
opt.keyserver_options.export_options,
|
||||
&keyblock, &data, &datalen);
|
||||
if (err)
|
||||
log_error (_("skipped \"%s\": %s\n"), kspec->d, gpg_strerror (err));
|
||||
else
|
||||
{
|
||||
if (keyserver->host)
|
||||
log_info (_("sending key %s to %s server %s\n"),
|
||||
keystr (keyblock->pkt->pkt.public_key->keyid),
|
||||
keyserver->scheme, keyserver->host);
|
||||
else
|
||||
log_info (_("sending key %s to %s\n"),
|
||||
keystr (keyblock->pkt->pkt.public_key->keyid),
|
||||
keyserver->uri);
|
||||
|
||||
err = gpg_dirmngr_ks_put (ctrl, data, datalen, keyblock);
|
||||
release_kbnode (keyblock);
|
||||
xfree (data);
|
||||
if (err)
|
||||
log_error (_("keyserver send failed: %s\n"), gpg_strerror (err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return err;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -2439,8 +1811,9 @@ keyserver_import_name (ctrl_t ctrl, const char *name,
|
||||
|
||||
append_to_strlist(&list,name);
|
||||
|
||||
rc = keyserver_work (ctrl, KS_GETNAME, list, NULL,
|
||||
0, fpr, fpr_len, keyserver);
|
||||
rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
|
||||
/* keyserver_work (ctrl, KS_GETNAME, list, NULL, */
|
||||
/* 0, fpr, fpr_len, keyserver); */
|
||||
|
||||
free_strlist(list);
|
||||
|
||||
@ -2513,8 +1886,9 @@ keyserver_import_ldap (ctrl_t ctrl,
|
||||
|
||||
append_to_strlist(&list,name);
|
||||
|
||||
rc = keyserver_work (ctrl, KS_GETNAME, list, NULL,
|
||||
0, fpr, fpr_len, keyserver);
|
||||
rc = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /*FIXME*/
|
||||
/* keyserver_work (ctrl, KS_GETNAME, list, NULL, */
|
||||
/* 0, fpr, fpr_len, keyserver); */
|
||||
|
||||
free_strlist(list);
|
||||
|
||||
|
@ -287,6 +287,10 @@ int parse_export_options(char *str,unsigned int *options,int noisy);
|
||||
int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options );
|
||||
int export_pubkeys_stream (ctrl_t ctrl, iobuf_t out, strlist_t users,
|
||||
kbnode_t *keyblock_out, unsigned int options );
|
||||
gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec,
|
||||
unsigned int options,
|
||||
kbnode_t *r_keyblock,
|
||||
void **r_data, size_t *r_datalen);
|
||||
int export_seckeys (ctrl_t ctrl, strlist_t users);
|
||||
int export_secsubkeys (ctrl_t ctrl, strlist_t users);
|
||||
|
||||
|
@ -1,3 +1,9 @@
|
||||
2011-01-20 Werner Koch <wk@g10code.com>
|
||||
|
||||
* gpgkeys_hkp.c (get_name): Remove test for KS_GETNAME. It is
|
||||
always true.
|
||||
(search_key): Remove test for KS_GETNAME. It is always false.
|
||||
|
||||
2009-08-26 Werner Koch <wk@g10code.com>
|
||||
|
||||
* gpgkeys_hkp.c: Include util.h.
|
||||
|
@ -340,7 +340,7 @@ get_name(const char *getkey)
|
||||
opt->path,
|
||||
appendable_path (opt->path,"/pks/lookup?op=get&options=mr&search="),
|
||||
searchkey_encoded,
|
||||
opt->action == KS_GETNAME? "&exact=on":"",
|
||||
"&exact=on",
|
||||
NULL);
|
||||
if(!request)
|
||||
{
|
||||
@ -429,7 +429,6 @@ search_key(const char *searchkey)
|
||||
appendable_path (opt->path, "/pks/lookup?op=index&options=mr&search="),
|
||||
hexprefix,
|
||||
searchkey_encoded,
|
||||
opt->action == KS_GETNAME? "&exact=on":"",
|
||||
NULL);
|
||||
if(!request)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user