mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
gpg: Make decryption with the OpenPGP card work.
* scd/app-common.h (APP_DECIPHER_INFO_NOPAD): New. * scd/app-openpgp.c (do_decipher): Add arg R_INFO. * scd/app-nks.c (do_decipher): Add arg R_INFO as a dummy. * scd/app.c (app_decipher): Add arg R_INFO. * scd/command.c (cmd_pkdecrypt): Print status line "PADDING". * agent/call-scd.c (padding_info_cb): New. (agent_card_pkdecrypt): Add arg R_PADDING. * agent/divert-scd.c (divert_pkdecrypt): Ditto. * agent/pkdecrypt.c (agent_pkdecrypt): Ditto. * agent/command.c (cmd_pkdecrypt): Print status line "PADDING". * g10/call-agent.c (padding_info_cb): New. (agent_pkdecrypt): Add arg R_PADDING. * g10/pubkey-enc.c (get_it): Use padding info. -- Decryption using a card never worked in gpg 2.1 because the information whether the pkcs#1 padding needs to be removed was not available. Gpg < 2.1 too this info from the secret sub key but that has gone in 2.1. Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
parent
04e2c83f18
commit
780ba32336
2
NEWS
2
NEWS
@ -22,6 +22,8 @@ Noteworthy changes in version 2.1.0-betaN (unreleased)
|
||||
|
||||
* Support installation as portable application under Windows.
|
||||
|
||||
* Fixed GPG to decrypt using an OpenPGP card.
|
||||
|
||||
|
||||
Noteworthy changes in version 2.1.0beta3 (2011-12-20)
|
||||
-----------------------------------------------------
|
||||
|
@ -370,7 +370,7 @@ int agent_pksign (ctrl_t ctrl, const char *cache_nonce,
|
||||
/*-- pkdecrypt.c --*/
|
||||
int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
|
||||
const unsigned char *ciphertext, size_t ciphertextlen,
|
||||
membuf_t *outbuf);
|
||||
membuf_t *outbuf, int *r_padding);
|
||||
|
||||
/*-- genkey.c --*/
|
||||
int check_passphrase_constraints (ctrl_t ctrl, const char *pw, int silent);
|
||||
@ -425,7 +425,7 @@ int divert_pksign (ctrl_t ctrl,
|
||||
int divert_pkdecrypt (ctrl_t ctrl,
|
||||
const unsigned char *cipher,
|
||||
const unsigned char *shadow_info,
|
||||
char **r_buf, size_t *r_len);
|
||||
char **r_buf, size_t *r_len, int *r_padding);
|
||||
int divert_generic_cmd (ctrl_t ctrl,
|
||||
const char *cmdline, void *assuan_context);
|
||||
int divert_writekey (ctrl_t ctrl, int force, const char *serialno,
|
||||
@ -459,7 +459,7 @@ int agent_card_pkdecrypt (ctrl_t ctrl,
|
||||
int (*getpin_cb)(void *, const char *, char*,size_t),
|
||||
void *getpin_cb_arg,
|
||||
const unsigned char *indata, size_t indatalen,
|
||||
char **r_buf, size_t *r_buflen);
|
||||
char **r_buf, size_t *r_buflen, int *r_padding);
|
||||
int agent_card_readcert (ctrl_t ctrl,
|
||||
const char *id, char **r_buf, size_t *r_buflen);
|
||||
int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf);
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* call-scd.c - fork of the scdaemon to do SC operations
|
||||
* Copyright (C) 2001, 2002, 2005, 2007, 2010,
|
||||
* 2011 Free Software Foundation, Inc.
|
||||
* Copyright (C) 2013 Werner Koch
|
||||
*
|
||||
* This file is part of GnuPG.
|
||||
*
|
||||
@ -874,14 +875,36 @@ agent_card_pksign (ctrl_t ctrl,
|
||||
return unlock_scd (ctrl, 0);
|
||||
}
|
||||
|
||||
/* Decipher INDATA using the current card. Note that the returned value is */
|
||||
|
||||
|
||||
|
||||
/* Check whether there is any padding info from scdaemon. */
|
||||
static gpg_error_t
|
||||
padding_info_cb (void *opaque, const char *line)
|
||||
{
|
||||
int *r_padding = opaque;
|
||||
const char *s;
|
||||
|
||||
if ((s=has_leading_keyword (line, "PADDING")))
|
||||
{
|
||||
*r_padding = atoi (s);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Decipher INDATA using the current card. Note that the returned
|
||||
value is not an s-expression but the raw data as returned by
|
||||
scdaemon. The padding information is stored at R_PADDING with -1
|
||||
for not known. */
|
||||
int
|
||||
agent_card_pkdecrypt (ctrl_t ctrl,
|
||||
const char *keyid,
|
||||
int (*getpin_cb)(void *, const char *, char*, size_t),
|
||||
void *getpin_cb_arg,
|
||||
const unsigned char *indata, size_t indatalen,
|
||||
char **r_buf, size_t *r_buflen)
|
||||
char **r_buf, size_t *r_buflen, int *r_padding)
|
||||
{
|
||||
int rc, i;
|
||||
char *p, line[ASSUAN_LINELENGTH];
|
||||
@ -890,6 +913,7 @@ agent_card_pkdecrypt (ctrl_t ctrl,
|
||||
size_t len;
|
||||
|
||||
*r_buf = NULL;
|
||||
*r_padding = -1; /* Unknown. */
|
||||
rc = start_scd (ctrl);
|
||||
if (rc)
|
||||
return rc;
|
||||
@ -923,7 +947,7 @@ agent_card_pkdecrypt (ctrl_t ctrl,
|
||||
rc = assuan_transact (ctrl->scd_local->ctx, line,
|
||||
membuf_data_cb, &data,
|
||||
inq_needpin, &inqparm,
|
||||
NULL, NULL);
|
||||
padding_info_cb, r_padding);
|
||||
if (inqparm.any_inq_seen && (gpg_err_code(rc) == GPG_ERR_CANCELED ||
|
||||
gpg_err_code(rc) == GPG_ERR_ASS_CANCELED))
|
||||
rc = cancel_inquire (ctrl, rc);
|
||||
|
@ -865,6 +865,7 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
|
||||
unsigned char *value;
|
||||
size_t valuelen;
|
||||
membuf_t outbuf;
|
||||
int padding;
|
||||
|
||||
(void)line;
|
||||
|
||||
@ -879,12 +880,19 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
|
||||
init_membuf (&outbuf, 512);
|
||||
|
||||
rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
|
||||
value, valuelen, &outbuf);
|
||||
value, valuelen, &outbuf, &padding);
|
||||
xfree (value);
|
||||
if (rc)
|
||||
clear_outbuf (&outbuf);
|
||||
else
|
||||
rc = write_and_clear_outbuf (ctx, &outbuf);
|
||||
{
|
||||
if (padding != -1)
|
||||
rc = print_assuan_status (ctx, "PADDING", "%d", padding);
|
||||
else
|
||||
rc = 0;
|
||||
if (!rc)
|
||||
rc = write_and_clear_outbuf (ctx, &outbuf);
|
||||
}
|
||||
xfree (ctrl->server_local->keydesc);
|
||||
ctrl->server_local->keydesc = NULL;
|
||||
return leave_cmd (ctx, rc);
|
||||
|
@ -383,12 +383,13 @@ divert_pksign (ctrl_t ctrl,
|
||||
|
||||
/* Decrypt the the value given asn an S-expression in CIPHER using the
|
||||
key identified by SHADOW_INFO and return the plaintext in an
|
||||
allocated buffer in R_BUF. */
|
||||
allocated buffer in R_BUF. The padding information is stored at
|
||||
R_PADDING with -1 for not known. */
|
||||
int
|
||||
divert_pkdecrypt (ctrl_t ctrl,
|
||||
const unsigned char *cipher,
|
||||
const unsigned char *shadow_info,
|
||||
char **r_buf, size_t *r_len)
|
||||
char **r_buf, size_t *r_len, int *r_padding)
|
||||
{
|
||||
int rc;
|
||||
char *kid;
|
||||
@ -399,6 +400,8 @@ divert_pkdecrypt (ctrl_t ctrl,
|
||||
char *plaintext;
|
||||
size_t plaintextlen;
|
||||
|
||||
*r_padding = -1;
|
||||
|
||||
s = cipher;
|
||||
if (*s != '(')
|
||||
return gpg_error (GPG_ERR_INV_SEXP);
|
||||
@ -436,7 +439,7 @@ divert_pkdecrypt (ctrl_t ctrl,
|
||||
|
||||
rc = agent_card_pkdecrypt (ctrl, kid, getpin_cb, ctrl,
|
||||
ciphertext, ciphertextlen,
|
||||
&plaintext, &plaintextlen);
|
||||
&plaintext, &plaintextlen, r_padding);
|
||||
if (!rc)
|
||||
{
|
||||
*r_buf = plaintext;
|
||||
|
@ -32,11 +32,12 @@
|
||||
|
||||
/* DECRYPT the stuff in ciphertext which is expected to be a S-Exp.
|
||||
Try to get the key from CTRL and write the decoded stuff back to
|
||||
OUTFP. */
|
||||
OUTFP. The padding information is stored at R_PADDING with -1
|
||||
for not known. */
|
||||
int
|
||||
agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
|
||||
const unsigned char *ciphertext, size_t ciphertextlen,
|
||||
membuf_t *outbuf)
|
||||
membuf_t *outbuf, int *r_padding)
|
||||
{
|
||||
gcry_sexp_t s_skey = NULL, s_cipher = NULL, s_plain = NULL;
|
||||
unsigned char *shadow_info = NULL;
|
||||
@ -44,6 +45,8 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
|
||||
char *buf = NULL;
|
||||
size_t len;
|
||||
|
||||
*r_padding = -1;
|
||||
|
||||
if (!ctrl->have_keygrip)
|
||||
{
|
||||
log_error ("speculative decryption not yet supported\n");
|
||||
@ -85,7 +88,8 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
|
||||
goto leave;
|
||||
}
|
||||
|
||||
rc = divert_pkdecrypt (ctrl, ciphertext, shadow_info, &buf, &len );
|
||||
rc = divert_pkdecrypt (ctrl, ciphertext, shadow_info,
|
||||
&buf, &len, r_padding);
|
||||
if (rc)
|
||||
{
|
||||
log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc));
|
||||
|
@ -897,10 +897,15 @@ Here is an example session:
|
||||
C: D (b 3F444677CA)))
|
||||
C: END
|
||||
S: # session key follows
|
||||
S: S PADDING 0
|
||||
S: D (value 1234567890ABCDEF0)
|
||||
S: OK descryption successful
|
||||
@end example
|
||||
|
||||
The “PADDING” status line is only send if gpg-agent can tell what kind
|
||||
of padding is used. As of now only the value 0 is used to indicate
|
||||
that the padding has been removed.
|
||||
|
||||
|
||||
@node Agent PKSIGN
|
||||
@subsection Signing a Hash
|
||||
|
@ -600,6 +600,10 @@ using the command
|
||||
|
||||
where @var{keyid} is the hexified ID of the key to be used.
|
||||
|
||||
If the card is ware of the apdding format a status line with padding
|
||||
information is send before the plaintext data. The key for this
|
||||
status line is @code{PADDING} with the only defined value being 0 and
|
||||
meaning padding has been removed.
|
||||
|
||||
@node Scdaemon GETATTR
|
||||
@subsection Read an attribute's value.
|
||||
|
@ -1816,17 +1816,34 @@ inq_ciphertext_cb (void *opaque, const char *line)
|
||||
}
|
||||
|
||||
|
||||
/* Check whether there is any padding info from the agent. */
|
||||
static gpg_error_t
|
||||
padding_info_cb (void *opaque, const char *line)
|
||||
{
|
||||
int *r_padding = opaque;
|
||||
const char *s;
|
||||
|
||||
if ((s=has_leading_keyword (line, "PADDING")))
|
||||
{
|
||||
*r_padding = atoi (s);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Call the agent to do a decrypt operation using the key identified
|
||||
by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the
|
||||
success the decoded value is stored verbatim at R_BUF and its
|
||||
length at R_BUF; the callers needs to release it. KEYID, MAINKEYID
|
||||
and PUBKEY_ALGO are used to construct additional promots or status
|
||||
messages. */
|
||||
messages. The padding information is stored at R_PADDING with -1
|
||||
for not known. */
|
||||
gpg_error_t
|
||||
agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
|
||||
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
|
||||
gcry_sexp_t s_ciphertext,
|
||||
unsigned char **r_buf, size_t *r_buflen)
|
||||
unsigned char **r_buf, size_t *r_buflen, int *r_padding)
|
||||
{
|
||||
gpg_error_t err;
|
||||
char line[ASSUAN_LINELENGTH];
|
||||
@ -1841,9 +1858,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
|
||||
dfltparm.keyinfo.mainkeyid = mainkeyid;
|
||||
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
|
||||
|
||||
if (!keygrip || strlen(keygrip) != 40 || !s_ciphertext || !r_buf || !r_buflen)
|
||||
if (!keygrip || strlen(keygrip) != 40
|
||||
|| !s_ciphertext || !r_buf || !r_buflen || !r_padding)
|
||||
return gpg_error (GPG_ERR_INV_VALUE);
|
||||
|
||||
*r_buf = NULL;
|
||||
*r_padding = -1;
|
||||
|
||||
err = start_agent (ctrl, 0);
|
||||
if (err)
|
||||
@ -1881,7 +1901,8 @@ agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
|
||||
return err;
|
||||
err = assuan_transact (agent_ctx, "PKDECRYPT",
|
||||
membuf_data_cb, &data,
|
||||
inq_ciphertext_cb, &parm, NULL, NULL);
|
||||
inq_ciphertext_cb, &parm,
|
||||
padding_info_cb, r_padding);
|
||||
xfree (parm.ciphertext);
|
||||
}
|
||||
if (err)
|
||||
|
@ -168,7 +168,8 @@ gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
|
||||
gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
|
||||
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
|
||||
gcry_sexp_t s_ciphertext,
|
||||
unsigned char **r_buf, size_t *r_buflen);
|
||||
unsigned char **r_buf, size_t *r_buflen,
|
||||
int *r_padding);
|
||||
|
||||
/* Retrieve a key encryption key. */
|
||||
gpg_error_t agent_keywrap_key (ctrl_t ctrl, int forexport,
|
||||
|
@ -146,7 +146,7 @@ get_it (PKT_pubkey_enc *enc, DEK *dek, PKT_public_key *sk, u32 *keyid)
|
||||
unsigned int n;
|
||||
size_t nframe;
|
||||
u16 csum, csum2;
|
||||
int card = 0;
|
||||
int padding;
|
||||
gcry_sexp_t s_data;
|
||||
char *desc;
|
||||
char *keygrip;
|
||||
@ -203,7 +203,7 @@ get_it (PKT_pubkey_enc *enc, DEK *dek, PKT_public_key *sk, u32 *keyid)
|
||||
desc = gpg_format_keydesc (sk, 0, 1);
|
||||
err = agent_pkdecrypt (NULL, keygrip,
|
||||
desc, sk->keyid, sk->main_keyid, sk->pubkey_algo,
|
||||
s_data, &frame, &nframe);
|
||||
s_data, &frame, &nframe, &padding);
|
||||
xfree (desc);
|
||||
gcry_sexp_release (s_data);
|
||||
if (err)
|
||||
@ -270,7 +270,7 @@ get_it (PKT_pubkey_enc *enc, DEK *dek, PKT_public_key *sk, u32 *keyid)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!card)
|
||||
if (padding)
|
||||
{
|
||||
if (n + 7 > nframe)
|
||||
{
|
||||
|
@ -34,6 +34,9 @@
|
||||
#define APP_CHANGE_FLAG_RESET 1
|
||||
#define APP_CHANGE_FLAG_NULLPIN 2
|
||||
|
||||
/* Bit flags set by the decipher function into R_INFO. */
|
||||
#define APP_DECIPHER_INFO_NOPAD 1 /* Padding has been removed. */
|
||||
|
||||
|
||||
struct app_local_s; /* Defined by all app-*.c. */
|
||||
|
||||
@ -93,10 +96,11 @@ struct app_ctx_s {
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen);
|
||||
gpg_error_t (*decipher) (app_t app, const char *keyidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen);
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen,
|
||||
unsigned int *r_info);
|
||||
gpg_error_t (*writecert) (app_t app, ctrl_t ctrl,
|
||||
const char *certid,
|
||||
gpg_error_t (*pincb)(void*,const char *,char **),
|
||||
@ -168,15 +172,16 @@ gpg_error_t app_sign (app_t app, const char *keyidstr, int hashalgo,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen );
|
||||
gpg_error_t app_auth (app_t app, const char *keyidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen);
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen);
|
||||
gpg_error_t app_decipher (app_t app, const char *keyidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen );
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen,
|
||||
unsigned int *r_info);
|
||||
gpg_error_t app_writecert (app_t app, ctrl_t ctrl,
|
||||
const char *certidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
|
@ -985,13 +985,16 @@ do_decipher (app_t app, const char *keyidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen )
|
||||
unsigned char **outdata, size_t *outdatalen,
|
||||
unsigned int *r_info)
|
||||
{
|
||||
int rc, i;
|
||||
int is_sigg = 0;
|
||||
int fid;
|
||||
int kid;
|
||||
|
||||
(void)r_info;
|
||||
|
||||
if (!keyidstr || !*keyidstr || !indatalen)
|
||||
return gpg_error (GPG_ERR_INV_VALUE);
|
||||
|
||||
|
@ -3598,7 +3598,8 @@ do_decipher (app_t app, const char *keyidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen )
|
||||
unsigned char **outdata, size_t *outdatalen,
|
||||
unsigned int *r_info)
|
||||
{
|
||||
int rc;
|
||||
unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */
|
||||
@ -3727,6 +3728,8 @@ do_decipher (app_t app, const char *keyidstr,
|
||||
&& app->card_version == 0x0200)
|
||||
log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)"
|
||||
" do not work with encryption keys > 2048 bits\n");
|
||||
|
||||
*r_info |= APP_DECIPHER_INFO_NOPAD;
|
||||
}
|
||||
|
||||
return rc;
|
||||
|
@ -801,10 +801,13 @@ app_decipher (app_t app, const char *keyidstr,
|
||||
gpg_error_t (*pincb)(void*, const char *, char **),
|
||||
void *pincb_arg,
|
||||
const void *indata, size_t indatalen,
|
||||
unsigned char **outdata, size_t *outdatalen )
|
||||
unsigned char **outdata, size_t *outdatalen,
|
||||
unsigned int *r_info)
|
||||
{
|
||||
gpg_error_t err;
|
||||
|
||||
*r_info = 0;
|
||||
|
||||
if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb)
|
||||
return gpg_error (GPG_ERR_INV_VALUE);
|
||||
if (!app->ref_count)
|
||||
@ -817,7 +820,8 @@ app_decipher (app_t app, const char *keyidstr,
|
||||
err = app->fnc.decipher (app, keyidstr,
|
||||
pincb, pincb_arg,
|
||||
indata, indatalen,
|
||||
outdata, outdatalen);
|
||||
outdata, outdatalen,
|
||||
r_info);
|
||||
unlock_reader (app->slot);
|
||||
if (opt.verbose)
|
||||
log_info ("operation decipher result: %s\n", gpg_strerror (err));
|
||||
|
@ -1089,6 +1089,7 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
|
||||
unsigned char *outdata;
|
||||
size_t outdatalen;
|
||||
char *keyidstr;
|
||||
unsigned int infoflags;
|
||||
|
||||
if ( IS_LOCKED (ctrl) )
|
||||
return gpg_error (GPG_ERR_LOCKED);
|
||||
@ -1103,7 +1104,7 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
|
||||
keyidstr,
|
||||
pin_cb, ctx,
|
||||
ctrl->in_data.value, ctrl->in_data.valuelen,
|
||||
&outdata, &outdatalen);
|
||||
&outdata, &outdatalen, &infoflags);
|
||||
|
||||
xfree (keyidstr);
|
||||
if (rc)
|
||||
@ -1112,6 +1113,13 @@ cmd_pkdecrypt (assuan_context_t ctx, char *line)
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the card driver told us that there is no padding, send a
|
||||
status line. If there is a padding it is assumed that the
|
||||
caller knows what padding is used. It would have been better
|
||||
to always send that information but for backward
|
||||
compatibility we can't do that. */
|
||||
if ((infoflags & APP_DECIPHER_INFO_NOPAD))
|
||||
send_status_direct (ctrl, "PADDING", "0");
|
||||
rc = assuan_send_data (ctx, outdata, outdatalen);
|
||||
xfree (outdata);
|
||||
if (rc)
|
||||
|
Loading…
x
Reference in New Issue
Block a user