- Cryptoflex now works with the new pkcs15init stuff
git-svn-id: https://www.opensc-project.org/svnp/opensc/trunk@452 c6295689-39f2-0310-b995-f0e70906c6a9
This commit is contained in:
parent
fb2532c0d1
commit
dc444cde54
|
@ -13,8 +13,7 @@ endif
|
|||
|
||||
libpkcs15init_la_SOURCES = \
|
||||
pkcs15-lib.c profile.c \
|
||||
pkcs15-gpk.c pkcs15-miocos.c
|
||||
#pkcs15-cflex.c
|
||||
pkcs15-gpk.c pkcs15-miocos.c pkcs15-cflex.c
|
||||
|
||||
include_HEADERS = pkcs15-init.h
|
||||
noinst_HEADERS = profile.h
|
||||
|
|
|
@ -1,111 +1,77 @@
|
|||
#
|
||||
# PKCS15 r/w profile for Cryptoflex cards
|
||||
#
|
||||
CardInfo
|
||||
Label "OpenSC Card"
|
||||
Manufacturer "OpenSC Project"
|
||||
MinPinLength 1
|
||||
MaxPinLength 8
|
||||
PinEncoding ascii-numeric
|
||||
PinPadChar 0x00
|
||||
PrKeyAccessFlags RSA 0x1D
|
||||
cardinfo {
|
||||
max-pin-length = 8;
|
||||
pin-encoding = ascii-numeric;
|
||||
pin-pad-char = 0x00;
|
||||
|
||||
# This is the AAK (ie. transport key) required for
|
||||
# creating files in the MF
|
||||
Key AUT1 0x0001 "=Muscle00"
|
||||
# This is the secure messaging key required for
|
||||
# creating files in the MF
|
||||
key AUT1 {
|
||||
value = "=Muscle00";
|
||||
}
|
||||
}
|
||||
|
||||
DF MF
|
||||
Path 3F00
|
||||
ACL *=AUT1
|
||||
# Define reasonable limits for PINs and PUK
|
||||
# Note that we do not set a file path or reference
|
||||
# here; that is done dynamically.
|
||||
PIN user-pin {
|
||||
attempts = 3;
|
||||
}
|
||||
PIN user-puk {
|
||||
attempts = 10;
|
||||
}
|
||||
|
||||
DF key1df
|
||||
Parent PKCS15-AppDF
|
||||
FileID 4B01
|
||||
Size 750 # Sufficient for a 1024-bit key
|
||||
ACL *=AUT1 FILES=NONE
|
||||
# Additional filesystem info.
|
||||
# This is added to the file system info specified in the
|
||||
# main profile.
|
||||
filesystem {
|
||||
DF MF {
|
||||
ACL = *=AUT1;
|
||||
|
||||
DF key2df
|
||||
Parent PKCS15-AppDF
|
||||
FileID 4B02
|
||||
Size 750 # Sufficient for a 1024-bit key
|
||||
ACL *=AUT1 FILES=NONE
|
||||
DF PKCS15-AppDF {
|
||||
DF keydir-1 {
|
||||
file-id = 4B01;
|
||||
size = 750; # Sufficient for a 1024-bit key
|
||||
EF pinfile-1 {
|
||||
file-id = 0000;
|
||||
size = 23;
|
||||
ACL = *=NEVER, UPDATE=AUT1;
|
||||
}
|
||||
EF template-private-key-1 {
|
||||
file-id = 0012;
|
||||
ACL = *=NEVER, CRYPTO=CHV1, UPDATE=AUT1;
|
||||
}
|
||||
}
|
||||
DF keydir-2 {
|
||||
file-id = 4B02;
|
||||
size = 750; # Sufficient for a 1024-bit key
|
||||
EF pinfile-2 {
|
||||
file-id = 0000;
|
||||
size = 23;
|
||||
ACL = *=NEVER, UPDATE=AUT1;
|
||||
}
|
||||
EF template-private-key-2 {
|
||||
file-id = 0012;
|
||||
ACL = *=NEVER, CRYPTO=CHV1, UPDATE=AUT1;
|
||||
}
|
||||
}
|
||||
EF template-public-key-1 {
|
||||
file-id = 5201;
|
||||
ACL = *=AUT1, READ=NONE;
|
||||
}
|
||||
EF template-public-key-2 {
|
||||
file-id = 5202;
|
||||
ACL = *=AUT1, READ=NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EF pinfile-chv1
|
||||
Parent key1df
|
||||
FileID 0000
|
||||
Structure transparent
|
||||
Size 23
|
||||
ACL *=NEVER UPDATE=AUT1
|
||||
|
||||
EF pinfile-chv2
|
||||
Parent key2df
|
||||
FileID 0000
|
||||
Structure transparent
|
||||
Size 23
|
||||
ACL *=NEVER UPDATE=AUT1
|
||||
|
||||
EF template-private-key-1
|
||||
Parent key1df
|
||||
FileID 0012
|
||||
Structure transparent
|
||||
Size 330
|
||||
ACL *=AUT1 READ=NEVER
|
||||
|
||||
EF template-private-key-2
|
||||
Parent key2df
|
||||
FileID 0012
|
||||
Structure transparent
|
||||
Size 330
|
||||
ACL *=AUT1 READ=NEVER
|
||||
|
||||
EF template-public-key-1
|
||||
Parent PKCS15-AppDF
|
||||
FileID 5201
|
||||
Structure transparent
|
||||
ACL *=AUT1 READ=NONE
|
||||
|
||||
EF template-public-key-2
|
||||
Parent PKCS15-AppDF
|
||||
FileID 5202
|
||||
Structure transparent
|
||||
ACL *=AUT1 READ=NONE
|
||||
|
||||
EF PKCS15-DIR
|
||||
ACL *=NEVER READ=NONE UPDATE=AUT1
|
||||
|
||||
EF PKCS15-ODF
|
||||
ACL *=NEVER READ=NONE UPDATE=AUT1
|
||||
|
||||
EF PKCS15-AODF
|
||||
ACL *=NEVER READ=NONE UPDATE=AUT1
|
||||
|
||||
EF PKCS15-PrKDF
|
||||
ACL *=NEVER READ=NONE UPDATE=AUT1
|
||||
|
||||
EF PKCS15-PuKDF
|
||||
ACL *=NEVER READ=NONE UPDATE=AUT1
|
||||
|
||||
EF PKCS15-CDF
|
||||
ACL *=NEVER READ=NONE UPDATE=AUT1
|
||||
|
||||
# CHV1. 3 attempts for the PIN, and 10 for the PUK
|
||||
PIN CHV1
|
||||
File pinfile-chv1
|
||||
Reference 0x01
|
||||
Attempts 3 10
|
||||
|
||||
# CHV2. 3 attempts for the PIN, and 10 for the PUK
|
||||
PIN CHV2
|
||||
File pinfile-chv2
|
||||
Reference 0x01
|
||||
Attempts 3 10
|
||||
|
||||
PrivateKey AuthKey
|
||||
Reference 0x00
|
||||
Index 1
|
||||
File template-private-key-1
|
||||
|
||||
PrivateKey SignKey
|
||||
Reference 0x00
|
||||
Index 1
|
||||
File template-private-key-2
|
||||
# Define an SO pin
|
||||
# This PIN is not used yet.
|
||||
#PIN sopin {
|
||||
# file = sopinfile;
|
||||
# reference = 0;
|
||||
#}
|
||||
|
|
|
@ -25,18 +25,12 @@ filesystem {
|
|||
ACL = *=NONE;
|
||||
|
||||
DF PKCS15-AppDF {
|
||||
EF pinfile-chv1 {
|
||||
EF pinfile {
|
||||
type = internal-ef;
|
||||
file-id = 5001;
|
||||
size = 20;
|
||||
ACL = *=NEVER;
|
||||
}
|
||||
EF pinfile-chv2 {
|
||||
type = internal-ef;
|
||||
file-id = 5002;
|
||||
size = 20;
|
||||
ACL = *=NEVER;
|
||||
}
|
||||
EF template-private-key {
|
||||
type = internal-ef;
|
||||
file-id = 4B01; # This is the base FileID
|
||||
|
|
|
@ -25,135 +25,105 @@
|
|||
#include <string.h>
|
||||
#include <openssl/bn.h>
|
||||
#include "opensc.h"
|
||||
#include "cardctl.h"
|
||||
#include "pkcs15-init.h"
|
||||
#include "util.h"
|
||||
#include "profile.h"
|
||||
|
||||
/*
|
||||
* Initialize the Application DF
|
||||
*/
|
||||
static int cflex_init_app(struct sc_profile *profile, struct sc_card *card,
|
||||
const u8 *pin, size_t pin_len, const u8 *puk, size_t puk_len)
|
||||
{
|
||||
/* Create the application DF */
|
||||
if (sc_pkcs15init_create_file(profile, card, profile->df_info->file))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the contents of a PIN file
|
||||
*/
|
||||
static int cflex_update_pin(struct sc_card *card, struct pin_info *info)
|
||||
static int cflex_update_pin(struct sc_profile *profile, struct sc_card *card,
|
||||
sc_file_t *file,
|
||||
const u8 *pin, size_t pin_len, int pin_tries,
|
||||
const u8 *puk, size_t puk_len, int puk_tries)
|
||||
{
|
||||
u8 buffer[23], *p = buffer;
|
||||
int r;
|
||||
size_t len;
|
||||
|
||||
if (!info->puk.tries_left) {
|
||||
error("Cryptoflex needs a PUK code");
|
||||
return SC_ERROR_INVALID_ARGUMENTS;
|
||||
}
|
||||
|
||||
memset(p, 0xFF, 3);
|
||||
p += 3;
|
||||
memset(p, info->pin.pad_char, 8);
|
||||
strncpy((char *) p, info->secret[0], 8);
|
||||
memset(p, profile->pin_pad_char, 8);
|
||||
strncpy((char *) p, pin, pin_len);
|
||||
p += 8;
|
||||
*p++ = info->pin.tries_left;
|
||||
*p++ = info->pin.tries_left;
|
||||
memset(p, info->puk.pad_char, 8);
|
||||
strncpy((char *) p, info->secret[1], 8);
|
||||
*p++ = pin_tries;
|
||||
*p++ = pin_tries;
|
||||
memset(p, profile->pin_pad_char, 8);
|
||||
strncpy((char *) p, puk, puk_len);
|
||||
p += 8;
|
||||
*p++ = info->puk.tries_left;
|
||||
*p++ = info->puk.tries_left;
|
||||
*p++ = puk_tries;
|
||||
*p++ = puk_tries;
|
||||
len = 23;
|
||||
|
||||
r = sc_update_binary(card, 0, buffer, len, 0);
|
||||
r = sc_pkcs15init_update_file(profile, card, file, buffer, len);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the PIN file and write the PINs
|
||||
* Store a PIN
|
||||
*/
|
||||
static int cflex_store_pin(struct sc_profile *profile, struct sc_card *card,
|
||||
struct pin_info *info)
|
||||
static int
|
||||
cflex_new_pin(struct sc_profile *profile, struct sc_card *card,
|
||||
struct sc_pkcs15_pin_info *info, unsigned int index,
|
||||
const u8 *pin, size_t pin_len,
|
||||
const u8 *puk, size_t puk_len)
|
||||
{
|
||||
struct sc_file *pinfile;
|
||||
int r;
|
||||
|
||||
sc_file_dup(&pinfile, info->file->file);
|
||||
|
||||
card->ctx->log_errors = 0;
|
||||
r = sc_select_file(card, &pinfile->path, NULL);
|
||||
card->ctx->log_errors = 1;
|
||||
if (r == SC_ERROR_FILE_NOT_FOUND) {
|
||||
/* Now create the file */
|
||||
if ((r = sc_pkcs15init_create_file(profile, card, pinfile)) < 0)
|
||||
goto out;
|
||||
/* The PIN EF is automatically selected */
|
||||
} else if (r < 0)
|
||||
goto out;
|
||||
|
||||
/* If messing with the PIN file requires any sort of
|
||||
* authentication, send it to the card now */
|
||||
r = sc_pkcs15init_authenticate(profile, card, pinfile, SC_AC_OP_UPDATE);
|
||||
if (r < 0)
|
||||
goto out;
|
||||
|
||||
r = cflex_update_pin(card, info);
|
||||
|
||||
out: sc_file_free(pinfile);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the Application DF and store the PINs
|
||||
*
|
||||
*/
|
||||
static int cflex_init_app(struct sc_profile *profile, struct sc_card *card)
|
||||
{
|
||||
struct pin_info *pin1, *pin2, *pin3;
|
||||
sc_file_t *pinfile;
|
||||
struct sc_pkcs15_pin_info tmpinfo;
|
||||
char template[30];
|
||||
int pin_tries, puk_tries;
|
||||
int r;
|
||||
|
||||
pin1 = sc_profile_find_pin(profile, "CHV1");
|
||||
pin2 = sc_profile_find_pin(profile, "CHV2");
|
||||
pin3 = sc_profile_find_pin(profile, "CHV3");
|
||||
if (pin1 == NULL) {
|
||||
fprintf(stderr, "No CHV1 defined\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
card->ctx->log_errors = 0;
|
||||
r = sc_select_file(card, &profile->df_info.file->path, NULL);
|
||||
card->ctx->log_errors = 1;
|
||||
if (r == SC_ERROR_FILE_NOT_FOUND) {
|
||||
/* Create the application DF */
|
||||
if (sc_pkcs15init_create_file(profile, card, profile->df_info.file))
|
||||
return 1;
|
||||
} else if (r < 0) {
|
||||
fprintf(stderr, "Unable to select application DF: %s\n",
|
||||
sc_strerror(r));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Store CHV3 (ie. SO PIN) */
|
||||
if (pin3) {
|
||||
if (cflex_store_pin(profile, card, pin3))
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Store CHV1 */
|
||||
if (cflex_store_pin(profile, card, pin1))
|
||||
return 1;
|
||||
|
||||
/* Store CHV2 */
|
||||
if (pin2) {
|
||||
if (cflex_store_pin(profile, card, pin2))
|
||||
return 1;
|
||||
index++;
|
||||
sprintf(template, "pinfile-%d", index);
|
||||
/* Profile must define a "pinfile" for each PIN */
|
||||
if (sc_profile_get_file(profile, template, &pinfile) < 0) {
|
||||
profile->cbs->error("Profile doesn't define \"%s\"", template);
|
||||
return SC_ERROR_INVALID_ARGUMENTS;
|
||||
}
|
||||
info->path = pinfile->path;
|
||||
if (info->path.len > 2)
|
||||
info->path.len -= 2;
|
||||
info->reference = 1;
|
||||
if (pin_len > 8)
|
||||
pin_len = 8;
|
||||
if (puk_len > 8)
|
||||
puk_len = 8;
|
||||
sc_profile_get_pin_info(profile, SC_PKCS15INIT_USER_PIN, &tmpinfo);
|
||||
pin_tries = tmpinfo.tries_left;
|
||||
sc_profile_get_pin_info(profile, SC_PKCS15INIT_USER_PUK, &tmpinfo);
|
||||
puk_tries = tmpinfo.tries_left;
|
||||
|
||||
return 0;
|
||||
r = cflex_update_pin(profile, card, pinfile, pin, pin_len, pin_tries,
|
||||
puk, puk_len, puk_tries);
|
||||
sc_file_free(pinfile);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allocate a file
|
||||
*/
|
||||
static int cflex_allocate_file(struct sc_profile *profile, struct sc_card *card,
|
||||
static int
|
||||
cflex_new_file(struct sc_profile *profile, struct sc_card *card,
|
||||
unsigned int type, unsigned int num,
|
||||
struct sc_file **out)
|
||||
{
|
||||
char name[64], *tag, *desc;
|
||||
struct sc_file *file;
|
||||
char name[64], *tag, *desc;
|
||||
|
||||
desc = tag = NULL;
|
||||
while (1) {
|
||||
|
@ -168,7 +138,7 @@ static int cflex_allocate_file(struct sc_profile *profile, struct sc_card *card,
|
|||
break;
|
||||
case SC_PKCS15_TYPE_CERT:
|
||||
desc = "certificate";
|
||||
tag = "data";
|
||||
tag = "certificate";
|
||||
break;
|
||||
case SC_PKCS15_TYPE_DATA_OBJECT:
|
||||
desc = "data object";
|
||||
|
@ -182,19 +152,20 @@ static int cflex_allocate_file(struct sc_profile *profile, struct sc_card *card,
|
|||
* the generic class (SC_PKCS15_TYPE_CERT)
|
||||
*/
|
||||
if (!(type & ~SC_PKCS15_TYPE_CLASS_MASK)) {
|
||||
error("File type not supported by card driver");
|
||||
profile->cbs->error("File type not supported by card driver");
|
||||
return SC_ERROR_INVALID_ARGUMENTS;
|
||||
}
|
||||
type &= SC_PKCS15_TYPE_CLASS_MASK;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "template-%s-%d", tag, num + 1);
|
||||
if (sc_profile_get_file(profile, name, out) < 0) {
|
||||
error("Profile doesn't define %s template (%s)",
|
||||
snprintf(name, sizeof(name), "template-%s-%d", tag, num+1);
|
||||
if (sc_profile_get_file(profile, name, &file) < 0) {
|
||||
profile->cbs->error("Profile doesn't define %s template '%s'\n",
|
||||
desc, name);
|
||||
return SC_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
*out = file;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -378,28 +349,42 @@ static int cflex_encode_public_key(RSA *rsa, u8 *key, size_t *keysize, int key_n
|
|||
}
|
||||
|
||||
/*
|
||||
* Store a RSA key on the card
|
||||
* Store a private key
|
||||
*/
|
||||
static int cflex_store_rsa_key(struct sc_profile *profile, struct sc_card *card,
|
||||
struct sc_key_template *info, RSA *rsa)
|
||||
static int
|
||||
cflex_new_key(struct sc_profile *profile, struct sc_card *card,
|
||||
EVP_PKEY *key, unsigned int index,
|
||||
struct sc_pkcs15_prkey_info *info)
|
||||
{
|
||||
u8 prv[1024], pub[1024];
|
||||
size_t prvsize, pubsize;
|
||||
struct sc_file *tmpfile;
|
||||
struct sc_file *keyfile = NULL, *tmpfile = NULL;
|
||||
RSA *rsa = NULL;
|
||||
int r;
|
||||
|
||||
if (key->type != EVP_PKEY_RSA) {
|
||||
profile->cbs->error("Cryptoflex supports only RSA keys.");
|
||||
return SC_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
rsa = EVP_PKEY_get1_RSA(key);
|
||||
r = cflex_encode_private_key(rsa, prv, &prvsize, 1);
|
||||
if (r)
|
||||
return -1;
|
||||
goto err;
|
||||
r = cflex_encode_public_key(rsa, pub, &pubsize, 1);
|
||||
if (r)
|
||||
return -1;
|
||||
info->file->size = prvsize;
|
||||
goto err;
|
||||
printf("Updating RSA private key...\n");
|
||||
r = sc_pkcs15init_update_file(profile, card, info->file, prv, prvsize);
|
||||
r = cflex_new_file(profile, card, SC_PKCS15_TYPE_PRKEY_RSA, index,
|
||||
&keyfile);
|
||||
if (r < 0)
|
||||
return r;
|
||||
sc_file_dup(&tmpfile, info->file);
|
||||
goto err;
|
||||
keyfile->size = prvsize;
|
||||
r = sc_pkcs15init_update_file(profile, card, keyfile, prv, prvsize);
|
||||
if (r < 0)
|
||||
goto err;
|
||||
info->path = keyfile->path;
|
||||
info->modulus_length = RSA_size(rsa)*8;
|
||||
sc_file_dup(&tmpfile, keyfile);
|
||||
sc_file_clear_acl_entries(tmpfile, SC_AC_OP_READ);
|
||||
sc_file_add_acl_entry(tmpfile, SC_AC_OP_READ, SC_AC_NONE, SC_AC_KEY_REF_NONE);
|
||||
tmpfile->path.len -= 2;
|
||||
|
@ -408,16 +393,18 @@ static int cflex_store_rsa_key(struct sc_profile *profile, struct sc_card *card,
|
|||
tmpfile->size = pubsize;
|
||||
printf("Updating RSA public key...\n");
|
||||
r = sc_pkcs15init_update_file(profile, card, tmpfile, pub, pubsize);
|
||||
sc_file_free(tmpfile);
|
||||
if (r)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
err:
|
||||
if (rsa)
|
||||
RSA_free(rsa);
|
||||
if (tmpfile)
|
||||
sc_file_free(tmpfile);
|
||||
return r;
|
||||
}
|
||||
|
||||
void bind_cflex_operations(struct pkcs15_init_operations *ops)
|
||||
{
|
||||
ops->init_app = cflex_init_app;
|
||||
ops->allocate_file = cflex_allocate_file;
|
||||
ops->store_rsa = cflex_store_rsa_key;
|
||||
}
|
||||
struct sc_pkcs15init_operations sc_pkcs15init_cflex_operations = {
|
||||
NULL,
|
||||
cflex_init_app,
|
||||
cflex_new_pin,
|
||||
cflex_new_key,
|
||||
cflex_new_file,
|
||||
};
|
||||
|
|
|
@ -88,9 +88,7 @@ static int do_select_parent(struct sc_profile *, struct sc_card *,
|
|||
/* Card specific functions */
|
||||
extern struct sc_pkcs15init_operations sc_pkcs15init_gpk_operations;
|
||||
extern struct sc_pkcs15init_operations sc_pkcs15init_miocos_operations;
|
||||
#if 0
|
||||
extern struct sc_pkcs15init_operations sc_pkcs15init_cflex_operations;
|
||||
#endif
|
||||
|
||||
static struct sc_pkcs15init_callbacks *callbacks = NULL;
|
||||
|
||||
|
@ -124,10 +122,8 @@ sc_pkcs15init_bind(struct sc_profile *profile,
|
|||
profile->ops = &sc_pkcs15init_gpk_operations;
|
||||
else if (!strcasecmp(driver, "MioCOS"))
|
||||
profile->ops = &sc_pkcs15init_miocos_operations;
|
||||
#if 0
|
||||
else if (!strcasecmp(driver, "flex"))
|
||||
profile->ops = &sc_pkcs15init_cflex_operations;
|
||||
#endif
|
||||
else {
|
||||
p15init_error("Unsupported card driver %s", driver);
|
||||
return SC_ERROR_NOT_SUPPORTED;
|
||||
|
|
|
@ -51,21 +51,21 @@ miocos_new_pin(struct sc_profile *profile, struct sc_card *card,
|
|||
const u8 *pin, size_t pin_len,
|
||||
const u8 *puk, size_t puk_len)
|
||||
{
|
||||
char template[18];
|
||||
sc_file_t *pinfile;
|
||||
struct sc_pkcs15_pin_info tmpinfo;
|
||||
struct sc_cardctl_miocos_ac_info ac_info;
|
||||
int r;
|
||||
|
||||
sprintf(template, "pinfile-chv%d", index + 1);
|
||||
/* Profile must define a "pinfile" for each PIN */
|
||||
if (sc_profile_get_file(profile, template, &pinfile) < 0) {
|
||||
profile->cbs->error("Profile doesn't define \"%s\"", template);
|
||||
/* Profile must define a "pinfile" */
|
||||
if (sc_profile_get_file(profile, "pinfile", &pinfile) < 0) {
|
||||
profile->cbs->error("Profile doesn't define \"pinfile\"");
|
||||
return SC_ERROR_INVALID_ARGUMENTS;
|
||||
}
|
||||
info->path = pinfile->path;
|
||||
if (info->path.len > 2)
|
||||
info->path.len -= 2;
|
||||
pinfile->id += index;
|
||||
pinfile->path.value[pinfile->path.len-1] += index;
|
||||
r = sc_pkcs15init_create_file(profile, card, pinfile);
|
||||
sc_file_free(pinfile);
|
||||
if (r)
|
||||
|
|
Loading…
Reference in New Issue