mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
Alow batch ode for gpgsm --gen-key.
Allow CSR generation using an existing key with gpgsm.
This commit is contained in:
parent
5505a81a19
commit
1925cb37f9
4
NEWS
4
NEWS
@ -3,7 +3,9 @@ Noteworthy changes in version 2.0.13
|
|||||||
|
|
||||||
This is a BETA version!
|
This is a BETA version!
|
||||||
|
|
||||||
*
|
* Minor bnug fixes
|
||||||
|
|
||||||
|
* gpgsm --gen-key now implements a --batch mode.
|
||||||
|
|
||||||
|
|
||||||
Noteworthy changes in version 2.0.12 (2009-06-17)
|
Noteworthy changes in version 2.0.12 (2009-06-17)
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
2009-07-01 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* sexputil.c (get_pk_algo_from_canon_sexp): New.
|
||||||
|
|
||||||
2009-06-29 Werner Koch <wk@g10code.com>
|
2009-06-29 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
* estream.c (BUFFER_ROUND_TO_BLOCK): Remove unused macro.
|
* estream.c (BUFFER_ROUND_TO_BLOCK): Remove unused macro.
|
||||||
|
@ -292,14 +292,8 @@ make_canon_sexp_from_rsa_pk (const void *m_arg, size_t mlen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Return the so called "keygrip" which is the SHA-1 hash of the
|
/* Return the so parameters of a public RSA key expressed as an
|
||||||
public key parameters expressed in a way depended on the algorithm.
|
canonical encoded S-expression. */
|
||||||
|
|
||||||
KEY is expected to be an canonical encoded S-expression with a
|
|
||||||
public or private key. KEYLEN is the length of that buffer.
|
|
||||||
|
|
||||||
GRIP must be at least 20 bytes long. On success 0 is returned, on
|
|
||||||
error an error code. */
|
|
||||||
gpg_error_t
|
gpg_error_t
|
||||||
get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
|
get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
|
||||||
unsigned char const **r_n, size_t *r_nlen,
|
unsigned char const **r_n, size_t *r_nlen,
|
||||||
@ -389,3 +383,47 @@ get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
|
|||||||
*r_elen = rsa_e_len;
|
*r_elen = rsa_e_len;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Return the algo of a public RSA expressed as an canonical encoded
|
||||||
|
S-expression. On error the algo is set to 0. */
|
||||||
|
gpg_error_t
|
||||||
|
get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
|
||||||
|
int *r_algo)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
const unsigned char *buf, *tok;
|
||||||
|
size_t buflen, toklen;
|
||||||
|
int depth;
|
||||||
|
|
||||||
|
*r_algo = 0;
|
||||||
|
|
||||||
|
buf = keydata;
|
||||||
|
buflen = keydatalen;
|
||||||
|
depth = 0;
|
||||||
|
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
|
||||||
|
return err;
|
||||||
|
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
|
||||||
|
return err;
|
||||||
|
if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen))
|
||||||
|
return gpg_error (GPG_ERR_BAD_PUBKEY);
|
||||||
|
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
|
||||||
|
return err;
|
||||||
|
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
|
||||||
|
return err;
|
||||||
|
if (!tok)
|
||||||
|
return gpg_error (GPG_ERR_BAD_PUBKEY);
|
||||||
|
|
||||||
|
if (toklen == 3 && !memcmp ("rsa", tok, toklen))
|
||||||
|
*r_algo = GCRY_PK_RSA;
|
||||||
|
else if (toklen == 3 && !memcmp ("dsa", tok, toklen))
|
||||||
|
*r_algo = GCRY_PK_DSA;
|
||||||
|
else if (toklen == 3 && !memcmp ("elg", tok, toklen))
|
||||||
|
*r_algo = GCRY_PK_ELG;
|
||||||
|
else if (toklen == 5 && !memcmp ("ecdsa", tok, toklen))
|
||||||
|
*r_algo = GCRY_PK_ECDSA;
|
||||||
|
else
|
||||||
|
return gpg_error (GPG_ERR_PUBKEY_ALGO);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -201,6 +201,9 @@ gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata,
|
|||||||
size_t *r_nlen,
|
size_t *r_nlen,
|
||||||
unsigned char const **r_e,
|
unsigned char const **r_e,
|
||||||
size_t *r_elen);
|
size_t *r_elen);
|
||||||
|
gpg_error_t get_pk_algo_from_canon_sexp (const unsigned char *keydata,
|
||||||
|
size_t keydatalen,
|
||||||
|
int *r_algo);
|
||||||
|
|
||||||
/*-- convert.c --*/
|
/*-- convert.c --*/
|
||||||
int hex2bin (const char *string, void *buffer, size_t length);
|
int hex2bin (const char *string, void *buffer, size_t length);
|
||||||
|
@ -165,9 +165,10 @@ use @samp{--help} to get a list of supported operations.
|
|||||||
@table @gnupgtabopt
|
@table @gnupgtabopt
|
||||||
@item --gen-key
|
@item --gen-key
|
||||||
@opindex gen-key
|
@opindex gen-key
|
||||||
This command allows the interactive creation of a certifcate signing
|
This command allows the creation of a certificate signing request. It
|
||||||
request. It is commonly used along with the @option{--output} option to
|
is commonly used along with the @option{--output} option to save the
|
||||||
save the created CSR into a file.
|
created CSR into a file. If used with the @option{--batch} the signing
|
||||||
|
is requested from a parameter file.
|
||||||
|
|
||||||
@item --list-keys
|
@item --list-keys
|
||||||
@itemx -k
|
@itemx -k
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
2009-07-01 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* certreqgen-ui.c (check_keygrip): New.
|
||||||
|
(gpgsm_gencertreq_tty): Allow using an existing key.
|
||||||
|
|
||||||
|
* gpgsm.c (open_es_fread): New.
|
||||||
|
(main) <aKeygen>: Implement --batch mode.
|
||||||
|
|
||||||
2009-06-24 Werner Koch <wk@g10code.com>
|
2009-06-24 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
* call-dirmngr.c (pattern_from_strlist): Remove dead assignment of N.
|
* call-dirmngr.c (pattern_from_strlist): Remove dead assignment of N.
|
||||||
|
@ -86,6 +86,39 @@ store_mb_lines (membuf_t *mb, membuf_t *lines)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Chech whether we have a key for the key with HEXGRIP. Returns NULL
|
||||||
|
if not or a string describing the type of the key (RSA, ELG, DSA,
|
||||||
|
etc..). */
|
||||||
|
static const char *
|
||||||
|
check_keygrip (ctrl_t ctrl, const char *hexgrip)
|
||||||
|
{
|
||||||
|
gpg_error_t err;
|
||||||
|
ksba_sexp_t public;
|
||||||
|
size_t publiclen;
|
||||||
|
int algo;
|
||||||
|
|
||||||
|
if (hexgrip[0] == '0' && hexgrip[1] == 'x')
|
||||||
|
hexgrip += 2;
|
||||||
|
|
||||||
|
err = gpgsm_agent_readkey (ctrl, 0, hexgrip, &public);
|
||||||
|
if (err)
|
||||||
|
return NULL;
|
||||||
|
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
|
||||||
|
|
||||||
|
get_pk_algo_from_canon_sexp (public, publiclen, &algo);
|
||||||
|
xfree (public);
|
||||||
|
|
||||||
|
switch (algo)
|
||||||
|
{
|
||||||
|
case GCRY_PK_RSA: return "RSA";
|
||||||
|
case GCRY_PK_DSA: return "DSA";
|
||||||
|
case GCRY_PK_ELG: return "ELG";
|
||||||
|
case GCRY_PK_ECDSA: return "ECDSA";
|
||||||
|
default: return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* This function is used to create a certificate request from the
|
/* This function is used to create a certificate request from the
|
||||||
command line. In the past the similar gpgsm-gencert.sh script has
|
command line. In the past the similar gpgsm-gencert.sh script has
|
||||||
been used for it; however that scripts requires a full Unix shell
|
been used for it; however that scripts requires a full Unix shell
|
||||||
@ -99,7 +132,7 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
int selection;
|
int selection;
|
||||||
estream_t fp = NULL;
|
estream_t fp = NULL;
|
||||||
int method;
|
int method;
|
||||||
char *keytype;
|
const char *keytype;
|
||||||
char *keygrip = NULL;
|
char *keygrip = NULL;
|
||||||
unsigned int nbits;
|
unsigned int nbits;
|
||||||
int minbits = 1024;
|
int minbits = 1024;
|
||||||
@ -112,11 +145,13 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
int i;
|
int i;
|
||||||
const char *s, *s2;
|
const char *s, *s2;
|
||||||
|
|
||||||
|
answer = NULL;
|
||||||
init_membuf (&mb_email, 100);
|
init_membuf (&mb_email, 100);
|
||||||
init_membuf (&mb_dns, 100);
|
init_membuf (&mb_dns, 100);
|
||||||
init_membuf (&mb_uri, 100);
|
init_membuf (&mb_uri, 100);
|
||||||
init_membuf (&mb_result, 512);
|
init_membuf (&mb_result, 512);
|
||||||
|
|
||||||
|
again:
|
||||||
/* Get the type of the key. */
|
/* Get the type of the key. */
|
||||||
tty_printf (_("Please select what kind of key you want:\n"));
|
tty_printf (_("Please select what kind of key you want:\n"));
|
||||||
tty_printf (_(" (%d) RSA\n"), 1 );
|
tty_printf (_(" (%d) RSA\n"), 1 );
|
||||||
@ -125,10 +160,10 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
xfree (answer);
|
||||||
answer = tty_get (_("Your selection? "));
|
answer = tty_get (_("Your selection? "));
|
||||||
tty_kill_prompt ();
|
tty_kill_prompt ();
|
||||||
selection = *answer? atoi (answer): 1;
|
selection = *answer? atoi (answer): 1;
|
||||||
xfree (answer);
|
|
||||||
}
|
}
|
||||||
while (!(selection >= 1 && selection <= 3));
|
while (!(selection >= 1 && selection <= 3));
|
||||||
method = selection;
|
method = selection;
|
||||||
@ -136,13 +171,14 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
/* Get size of the key. */
|
/* Get size of the key. */
|
||||||
if (method == 1)
|
if (method == 1)
|
||||||
{
|
{
|
||||||
keytype = xstrdup ("RSA");
|
keytype = "RSA";
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
xfree (answer);
|
||||||
answer = tty_getf (_("What keysize do you want? (%u) "), defbits);
|
answer = tty_getf (_("What keysize do you want? (%u) "), defbits);
|
||||||
tty_kill_prompt ();
|
tty_kill_prompt ();
|
||||||
|
trim_spaces (answer);
|
||||||
nbits = *answer? atoi (answer): defbits;
|
nbits = *answer? atoi (answer): defbits;
|
||||||
xfree (answer);
|
|
||||||
if (nbits < minbits || nbits > maxbits)
|
if (nbits < minbits || nbits > maxbits)
|
||||||
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
|
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
|
||||||
"RSA", minbits, maxbits);
|
"RSA", minbits, maxbits);
|
||||||
@ -159,17 +195,34 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
}
|
}
|
||||||
else if (method == 2)
|
else if (method == 2)
|
||||||
{
|
{
|
||||||
tty_printf ("Not yet supported; "
|
for (;;)
|
||||||
"use the gpgsm-gencert.sh script instead\n");
|
{
|
||||||
keytype = xstrdup ("RSA");
|
xfree (answer);
|
||||||
nbits = defbits; /* We need a dummy value. */
|
answer = tty_get (_("Enter the keygrip: "));
|
||||||
|
tty_kill_prompt ();
|
||||||
|
trim_spaces (answer);
|
||||||
|
|
||||||
|
if (!*answer)
|
||||||
|
goto again;
|
||||||
|
else if (strlen (answer) != 40 &&
|
||||||
|
!(answer[0] == '0' && answer[1] == 'x'
|
||||||
|
&& strlen (answer+2) == 40))
|
||||||
|
tty_printf (_("Not a valid keygrip (expecting 40 hex digits)\n"));
|
||||||
|
else if (!(keytype = check_keygrip (ctrl, answer)) )
|
||||||
|
tty_printf (_("No key with this keygrip\n"));
|
||||||
|
else
|
||||||
|
break; /* Okay. */
|
||||||
|
}
|
||||||
|
nbits = 1024; /* A dummy value is sufficient. */
|
||||||
|
xfree (keygrip);
|
||||||
|
keygrip = answer;
|
||||||
|
answer = NULL;
|
||||||
}
|
}
|
||||||
else /* method == 3 */
|
else /* method == 3 */
|
||||||
{
|
{
|
||||||
tty_printf ("Not yet supported; "
|
tty_printf ("Not yet supported; "
|
||||||
"use the gpgsm-gencert.sh script instead\n");
|
"use the gpgsm-gencert.sh script instead\n");
|
||||||
keytype = xstrdup ("card:foobar");
|
goto again;
|
||||||
nbits = defbits; /* We need a dummy value. */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ask for the key usage. */
|
/* Ask for the key usage. */
|
||||||
@ -179,10 +232,11 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
tty_printf (_(" (%d) encrypt\n"), 3 );
|
tty_printf (_(" (%d) encrypt\n"), 3 );
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
xfree (answer);
|
||||||
answer = tty_get (_("Your selection? "));
|
answer = tty_get (_("Your selection? "));
|
||||||
tty_kill_prompt ();
|
tty_kill_prompt ();
|
||||||
|
trim_spaces (answer);
|
||||||
selection = *answer? atoi (answer): 1;
|
selection = *answer? atoi (answer): 1;
|
||||||
xfree (answer);
|
|
||||||
switch (selection)
|
switch (selection)
|
||||||
{
|
{
|
||||||
case 1: keyusage = "sign, encrypt"; break;
|
case 1: keyusage = "sign, encrypt"; break;
|
||||||
@ -194,7 +248,6 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
while (!keyusage);
|
while (!keyusage);
|
||||||
|
|
||||||
/* Get the subject name. */
|
/* Get the subject name. */
|
||||||
answer = NULL;
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
size_t erroff, errlen;
|
size_t erroff, errlen;
|
||||||
@ -303,7 +356,7 @@ gpgsm_gencertreq_tty (ctrl_t ctrl, FILE *output_fp)
|
|||||||
log_error (_("resource problem: out of core\n"));
|
log_error (_("resource problem: out of core\n"));
|
||||||
leave:
|
leave:
|
||||||
es_fclose (fp);
|
es_fclose (fp);
|
||||||
xfree (keytype);
|
xfree (answer);
|
||||||
xfree (subject_name);
|
xfree (subject_name);
|
||||||
xfree (keygrip);
|
xfree (keygrip);
|
||||||
xfree (get_membuf (&mb_email, NULL));
|
xfree (get_membuf (&mb_email, NULL));
|
||||||
|
58
sm/gpgsm.c
58
sm/gpgsm.c
@ -417,6 +417,7 @@ static void set_cmd (enum cmd_and_opt_values *ret_cmd,
|
|||||||
static void emergency_cleanup (void);
|
static void emergency_cleanup (void);
|
||||||
static int check_special_filename (const char *fname, int for_write);
|
static int check_special_filename (const char *fname, int for_write);
|
||||||
static int open_read (const char *filename);
|
static int open_read (const char *filename);
|
||||||
|
static estream_t open_es_fread (const char *filename);
|
||||||
static FILE *open_fwrite (const char *filename);
|
static FILE *open_fwrite (const char *filename);
|
||||||
static estream_t open_es_fwrite (const char *filename);
|
static estream_t open_es_fwrite (const char *filename);
|
||||||
static void run_protect_tool (int argc, char **argv);
|
static void run_protect_tool (int argc, char **argv);
|
||||||
@ -1770,10 +1771,28 @@ main ( int argc, char **argv)
|
|||||||
|
|
||||||
case aKeygen: /* Generate a key; well kind of. */
|
case aKeygen: /* Generate a key; well kind of. */
|
||||||
{
|
{
|
||||||
FILE *fp = open_fwrite (opt.outfile?opt.outfile:"-");
|
estream_t fpin = NULL;
|
||||||
gpgsm_gencertreq_tty (&ctrl, fp);
|
FILE *fpout;
|
||||||
if (fp != stdout)
|
|
||||||
fclose (fp);
|
if (opt.batch)
|
||||||
|
{
|
||||||
|
if (!argc) /* Create from stdin. */
|
||||||
|
fpin = open_es_fread ("-");
|
||||||
|
else if (argc == 1) /* From file. */
|
||||||
|
fpin = open_es_fread (*argv);
|
||||||
|
else
|
||||||
|
wrong_args ("--gen-key --batch [parmfile]");
|
||||||
|
}
|
||||||
|
|
||||||
|
fpout = open_fwrite (opt.outfile?opt.outfile:"-");
|
||||||
|
|
||||||
|
if (fpin)
|
||||||
|
gpgsm_genkey (&ctrl, fpin, fpout);
|
||||||
|
else
|
||||||
|
gpgsm_gencertreq_tty (&ctrl, fpout);
|
||||||
|
|
||||||
|
if (fpout != stdout)
|
||||||
|
fclose (fpout);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -1976,6 +1995,37 @@ open_read (const char *filename)
|
|||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Same as open_read but return an estream_t. */
|
||||||
|
static estream_t
|
||||||
|
open_es_fread (const char *filename)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
estream_t fp;
|
||||||
|
|
||||||
|
if (filename[0] == '-' && !filename[1])
|
||||||
|
fd = fileno (stdin);
|
||||||
|
else
|
||||||
|
fd = check_special_filename (filename, 0);
|
||||||
|
if (fd != -1)
|
||||||
|
{
|
||||||
|
fp = es_fdopen_nc (fd, "rb");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno));
|
||||||
|
gpgsm_exit (2);
|
||||||
|
}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
fp = es_fopen (filename, "rb");
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
log_error (_("can't open `%s': %s\n"), filename, strerror (errno));
|
||||||
|
gpgsm_exit (2);
|
||||||
|
}
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Open FILENAME for fwrite and return the stream. Stop with an error
|
/* Open FILENAME for fwrite and return the stream. Stop with an error
|
||||||
message in case of problems. "-" denotes stdout and if special
|
message in case of problems. "-" denotes stdout and if special
|
||||||
filenames are allowed the given fd is opened instead. Caller must
|
filenames are allowed the given fd is opened instead. Caller must
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
2009-06-30 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
|
* ccidmon.c (parse_line_sniffusb): Take also TAB as delimiter.
|
||||||
|
|
||||||
2009-06-29 Werner Koch <wk@g10code.com>
|
2009-06-29 Werner Koch <wk@g10code.com>
|
||||||
|
|
||||||
* ccidmon.c (parse_line_sniffusb): New.
|
* ccidmon.c (parse_line_sniffusb): New.
|
||||||
|
@ -702,13 +702,13 @@ parse_line_sniffusb (char *line, unsigned int lineno)
|
|||||||
if (debug)
|
if (debug)
|
||||||
printf ("line[%u] =`%s'\n", lineno, line);
|
printf ("line[%u] =`%s'\n", lineno, line);
|
||||||
|
|
||||||
p = strtok (line, " ");
|
p = strtok (line, " \t");
|
||||||
if (!p)
|
if (!p)
|
||||||
return;
|
return;
|
||||||
p = strtok (NULL, " ");
|
p = strtok (NULL, " \t");
|
||||||
if (!p)
|
if (!p)
|
||||||
return;
|
return;
|
||||||
p = strtok (NULL, " ");
|
p = strtok (NULL, " \t");
|
||||||
if (!p)
|
if (!p)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -720,7 +720,7 @@ parse_line_sniffusb (char *line, unsigned int lineno)
|
|||||||
unsigned int value;
|
unsigned int value;
|
||||||
|
|
||||||
length = databuffer.count;
|
length = databuffer.count;
|
||||||
while ((p=strtok (NULL, " ")))
|
while ((p=strtok (NULL, " \t")))
|
||||||
{
|
{
|
||||||
if (!hexdigitp (p[0]) || !hexdigitp (p[1]))
|
if (!hexdigitp (p[0]) || !hexdigitp (p[1]))
|
||||||
{
|
{
|
||||||
@ -745,7 +745,7 @@ parse_line_sniffusb (char *line, unsigned int lineno)
|
|||||||
flush_data ();
|
flush_data ();
|
||||||
|
|
||||||
*databuffer.address = 0;
|
*databuffer.address = 0;
|
||||||
while ((p=strtok (NULL, " (,)")))
|
while ((p=strtok (NULL, " \t(,)")))
|
||||||
{
|
{
|
||||||
if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN"))
|
if (!strcmp (p, "USBD_TRANSFER_DIRECTION_IN"))
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user