mirror of
git://git.gnupg.org/gnupg.git
synced 2025-02-01 16:33:02 +01:00
scd: PC/SC pinpad input improvement.
* scd/apdu.c (struct reader_table_s): Add members: PINMIN, PINMAX, and PINPAD_VERLEN_SUPPORTED. (CM_IOCTL_VENDOR_IFD_EXCHANGE, FEATURE_GET_TLV_PROPERTIES, PCSCv2_PART10_PROPERTY_*): New. (new_reader_slot): Initialize pinpad_varlen_supported, pinmin, pinmax. (pcsc_vendor_specific_init): New. (open_pcsc_reader_direct, open_pcsc_reader_wrapped): Call pcsc_vendor_specific_init. (check_pcsc_pinpad): Not detect here but use the result of pcsc_vendor_specific_init. (pcsc_pinpad_verify, pcsc_pinpad_modify): Specify bNumberMessage. -- (cherry picked from commit 95a3bffeaf07e8bf9487d4b165c336d166236fc1) Signed-off-by: NIIBE Yutaka --
This commit is contained in:
parent
6acb1d06f0
commit
755f92e2af
230
scd/apdu.c
230
scd/apdu.c
@ -117,6 +117,8 @@ struct reader_table_s {
|
|||||||
pcsc_dword_t protocol;
|
pcsc_dword_t protocol;
|
||||||
pcsc_dword_t verify_ioctl;
|
pcsc_dword_t verify_ioctl;
|
||||||
pcsc_dword_t modify_ioctl;
|
pcsc_dword_t modify_ioctl;
|
||||||
|
int pinmin;
|
||||||
|
int pinmax;
|
||||||
#ifdef NEED_PCSC_WRAPPER
|
#ifdef NEED_PCSC_WRAPPER
|
||||||
int req_fd;
|
int req_fd;
|
||||||
int rsp_fd;
|
int rsp_fd;
|
||||||
@ -134,6 +136,9 @@ struct reader_table_s {
|
|||||||
int status;
|
int status;
|
||||||
int is_t0; /* True if we know that we are running T=0. */
|
int is_t0; /* True if we know that we are running T=0. */
|
||||||
int is_spr532; /* True if we know that the reader is a SPR532. */
|
int is_spr532; /* True if we know that the reader is a SPR532. */
|
||||||
|
int pinpad_varlen_supported; /* True if we know that the reader
|
||||||
|
supports variable length pinpad
|
||||||
|
input. */
|
||||||
unsigned char atr[33];
|
unsigned char atr[33];
|
||||||
size_t atrlen; /* A zero length indicates that the ATR has
|
size_t atrlen; /* A zero length indicates that the ATR has
|
||||||
not yet been read; i.e. the card is not
|
not yet been read; i.e. the card is not
|
||||||
@ -226,8 +231,17 @@ static char (* DLSTDCALL CT_close) (unsigned short ctn);
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
|
#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
|
||||||
|
#define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1)
|
||||||
#define FEATURE_VERIFY_PIN_DIRECT 0x06
|
#define FEATURE_VERIFY_PIN_DIRECT 0x06
|
||||||
#define FEATURE_MODIFY_PIN_DIRECT 0x07
|
#define FEATURE_MODIFY_PIN_DIRECT 0x07
|
||||||
|
#define FEATURE_GET_TLV_PROPERTIES 0x12
|
||||||
|
|
||||||
|
#define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2
|
||||||
|
#define PCSCv2_PART10_PROPERTY_bTimeOut2 3
|
||||||
|
#define PCSCv2_PART10_PROPERTY_bMinPINSize 6
|
||||||
|
#define PCSCv2_PART10_PROPERTY_bMaxPINSize 7
|
||||||
|
#define PCSCv2_PART10_PROPERTY_wIdVendor 11
|
||||||
|
#define PCSCv2_PART10_PROPERTY_wIdProduct 12
|
||||||
|
|
||||||
|
|
||||||
/* The PC/SC error is defined as a long as per specs. Due to left
|
/* The PC/SC error is defined as a long as per specs. Due to left
|
||||||
@ -424,6 +438,7 @@ new_reader_slot (void)
|
|||||||
reader_table[reader].last_status = 0;
|
reader_table[reader].last_status = 0;
|
||||||
reader_table[reader].is_t0 = 1;
|
reader_table[reader].is_t0 = 1;
|
||||||
reader_table[reader].is_spr532 = 0;
|
reader_table[reader].is_spr532 = 0;
|
||||||
|
reader_table[reader].pinpad_varlen_supported = 0;
|
||||||
#ifdef NEED_PCSC_WRAPPER
|
#ifdef NEED_PCSC_WRAPPER
|
||||||
reader_table[reader].pcsc.req_fd = -1;
|
reader_table[reader].pcsc.req_fd = -1;
|
||||||
reader_table[reader].pcsc.rsp_fd = -1;
|
reader_table[reader].pcsc.rsp_fd = -1;
|
||||||
@ -431,6 +446,8 @@ new_reader_slot (void)
|
|||||||
#endif
|
#endif
|
||||||
reader_table[reader].pcsc.verify_ioctl = 0;
|
reader_table[reader].pcsc.verify_ioctl = 0;
|
||||||
reader_table[reader].pcsc.modify_ioctl = 0;
|
reader_table[reader].pcsc.modify_ioctl = 0;
|
||||||
|
reader_table[reader].pcsc.pinmin = -1;
|
||||||
|
reader_table[reader].pcsc.pinmax = -1;
|
||||||
|
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
@ -1694,6 +1711,132 @@ reset_pcsc_reader (int slot)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Examine reader specific parameters and initialize. This is mostly
|
||||||
|
for pinpad input. Called at opening the connection to the reader. */
|
||||||
|
static int
|
||||||
|
pcsc_vendor_specific_init (int slot)
|
||||||
|
{
|
||||||
|
unsigned char buf[256];
|
||||||
|
pcsc_dword_t len;
|
||||||
|
int sw;
|
||||||
|
int vendor = 0;
|
||||||
|
int product = 0;
|
||||||
|
pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1;
|
||||||
|
unsigned char *p;
|
||||||
|
|
||||||
|
len = sizeof (buf);
|
||||||
|
sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
|
||||||
|
if (sw)
|
||||||
|
{
|
||||||
|
log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n",
|
||||||
|
sw);
|
||||||
|
return SW_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p = buf;
|
||||||
|
while (p < buf + len)
|
||||||
|
{
|
||||||
|
unsigned char code = *p++;
|
||||||
|
int l = *p++;
|
||||||
|
unsigned int v = 0;
|
||||||
|
|
||||||
|
if (l == 1)
|
||||||
|
v = p[0];
|
||||||
|
else if (l == 2)
|
||||||
|
v = ((p[0] << 8) | p[1]);
|
||||||
|
else if (l == 4)
|
||||||
|
v = ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]);
|
||||||
|
|
||||||
|
if (code == FEATURE_VERIFY_PIN_DIRECT)
|
||||||
|
reader_table[slot].pcsc.verify_ioctl = v;
|
||||||
|
else if (code == FEATURE_MODIFY_PIN_DIRECT)
|
||||||
|
reader_table[slot].pcsc.modify_ioctl = v;
|
||||||
|
else if (code == FEATURE_GET_TLV_PROPERTIES)
|
||||||
|
get_tlv_ioctl = v;
|
||||||
|
|
||||||
|
if (DBG_CARD_IO)
|
||||||
|
log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v);
|
||||||
|
|
||||||
|
p += l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For system which doesn't support GET_TLV_PROPERTIES,
|
||||||
|
* we put some heuristics here.
|
||||||
|
*/
|
||||||
|
if (reader_table[slot].rdrname
|
||||||
|
&& strstr (reader_table[slot].rdrname, "SPRx32"))
|
||||||
|
{
|
||||||
|
reader_table[slot].is_spr532 = 1;
|
||||||
|
reader_table[slot].pinpad_varlen_supported = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (get_tlv_ioctl == (pcsc_dword_t)-1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
len = sizeof (buf);
|
||||||
|
sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len);
|
||||||
|
if (sw)
|
||||||
|
{
|
||||||
|
log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw);
|
||||||
|
return SW_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
while (p < buf + len)
|
||||||
|
{
|
||||||
|
unsigned char tag = *p++;
|
||||||
|
int l = *p++;
|
||||||
|
unsigned int v = 0;
|
||||||
|
|
||||||
|
/* Umm... here is little endian, while the encoding above is big. */
|
||||||
|
if (l == 1)
|
||||||
|
v = p[0];
|
||||||
|
else if (l == 2)
|
||||||
|
v = ((p[1] << 8) | p[0]);
|
||||||
|
else if (l == 4)
|
||||||
|
v = ((p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
|
||||||
|
|
||||||
|
if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize)
|
||||||
|
reader_table[slot].pcsc.pinmin = v;
|
||||||
|
else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize)
|
||||||
|
reader_table[slot].pcsc.pinmax = v;
|
||||||
|
else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor)
|
||||||
|
vendor = v;
|
||||||
|
else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct)
|
||||||
|
product = v;
|
||||||
|
|
||||||
|
if (DBG_CARD_IO)
|
||||||
|
log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v);
|
||||||
|
|
||||||
|
p += l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vendor == 0x0982 && product == 0x0008) /* Vega Alpha */
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Please read the comment of ccid_vendor_specific_init in
|
||||||
|
* ccid-driver.c.
|
||||||
|
*/
|
||||||
|
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
|
||||||
|
sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
|
||||||
|
cmd, sizeof (cmd), NULL, 0);
|
||||||
|
if (sw)
|
||||||
|
return SW_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
else if (vendor == 0x04e6 && product == 0xe003) /* SCM SPR532 */
|
||||||
|
{
|
||||||
|
reader_table[slot].is_spr532 = 1;
|
||||||
|
reader_table[slot].pinpad_varlen_supported = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Open the PC/SC reader without using the wrapper. Returns -1 on
|
/* Open the PC/SC reader without using the wrapper. Returns -1 on
|
||||||
error or a slot number for the reader. */
|
error or a slot number for the reader. */
|
||||||
#ifndef NEED_PCSC_WRAPPER
|
#ifndef NEED_PCSC_WRAPPER
|
||||||
@ -1792,6 +1935,7 @@ open_pcsc_reader_direct (const char *portstr)
|
|||||||
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
|
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
|
||||||
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
|
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
|
||||||
|
|
||||||
|
pcsc_vendor_specific_init (slot);
|
||||||
dump_reader_status (slot);
|
dump_reader_status (slot);
|
||||||
unlock_slot (slot);
|
unlock_slot (slot);
|
||||||
return slot;
|
return slot;
|
||||||
@ -1996,6 +2140,8 @@ open_pcsc_reader_wrapped (const char *portstr)
|
|||||||
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
|
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
|
||||||
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
|
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
|
||||||
|
|
||||||
|
pcsc_vendor_specific_init (slot);
|
||||||
|
|
||||||
/* Read the status so that IS_T0 will be set. */
|
/* Read the status so that IS_T0 will be set. */
|
||||||
pcsc_get_status (slot, &dummy_status);
|
pcsc_get_status (slot, &dummy_status);
|
||||||
|
|
||||||
@ -2035,68 +2181,28 @@ open_pcsc_reader (const char *portstr)
|
|||||||
static int
|
static int
|
||||||
check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo)
|
check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo)
|
||||||
{
|
{
|
||||||
unsigned char buf[256];
|
int r;
|
||||||
pcsc_dword_t len = 256;
|
|
||||||
int sw;
|
|
||||||
|
|
||||||
/* Hack to identify the SCM SPR532 and SPR332 readers which support
|
pininfo->minlen = reader_table[slot].pcsc.pinmin;
|
||||||
variable length PIN input.
|
pininfo->maxlen = reader_table[slot].pcsc.pinmax;
|
||||||
FIXME: Figure out whether there is a feature attribute for this.
|
|
||||||
Alternatively use the USB ids to detect known readers. */
|
|
||||||
if (reader_table[slot].rdrname
|
|
||||||
&& strstr (reader_table[slot].rdrname, "SPRx32"))
|
|
||||||
{
|
|
||||||
reader_table[slot].is_spr532 = 1;
|
|
||||||
pininfo->fixedlen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
check_again:
|
if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0)
|
||||||
if (command == ISO7816_VERIFY)
|
|| (command == ISO7816_CHANGE_REFERENCE_DATA
|
||||||
{
|
&& reader_table[slot].pcsc.modify_ioctl != 0))
|
||||||
if (reader_table[slot].pcsc.verify_ioctl == (pcsc_dword_t)-1)
|
r = 0; /* Success */
|
||||||
return SW_NOT_SUPPORTED;
|
|
||||||
else if (reader_table[slot].pcsc.verify_ioctl != 0)
|
|
||||||
return 0; /* Success */
|
|
||||||
}
|
|
||||||
else if (command == ISO7816_CHANGE_REFERENCE_DATA)
|
|
||||||
{
|
|
||||||
if (reader_table[slot].pcsc.modify_ioctl == (pcsc_dword_t)-1)
|
|
||||||
return SW_NOT_SUPPORTED;
|
|
||||||
else if (reader_table[slot].pcsc.modify_ioctl != 0)
|
|
||||||
return 0; /* Success */
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
return SW_NOT_SUPPORTED;
|
r = SW_NOT_SUPPORTED;
|
||||||
|
|
||||||
reader_table[slot].pcsc.verify_ioctl = (pcsc_dword_t)-1;
|
if (DBG_CARD_IO)
|
||||||
reader_table[slot].pcsc.modify_ioctl = (pcsc_dword_t)-1;
|
log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n",
|
||||||
|
(unsigned int)command, r);
|
||||||
|
|
||||||
sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
|
if (reader_table[slot].pinpad_varlen_supported)
|
||||||
if (sw)
|
pininfo->fixedlen = 0;
|
||||||
return SW_NOT_SUPPORTED;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
unsigned char *p = buf;
|
|
||||||
|
|
||||||
while (p < buf + len)
|
return r;
|
||||||
{
|
|
||||||
unsigned char code = *p++;
|
|
||||||
|
|
||||||
p++; /* Skip length */
|
|
||||||
if (code == FEATURE_VERIFY_PIN_DIRECT)
|
|
||||||
reader_table[slot].pcsc.verify_ioctl
|
|
||||||
= (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
|
||||||
else if (code == FEATURE_MODIFY_PIN_DIRECT)
|
|
||||||
reader_table[slot].pcsc.modify_ioctl
|
|
||||||
= (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
|
||||||
p += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goto check_again;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#define PIN_VERIFY_STRUCTURE_SIZE 24
|
#define PIN_VERIFY_STRUCTURE_SIZE 24
|
||||||
static int
|
static int
|
||||||
pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
|
pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
|
||||||
@ -2133,8 +2239,8 @@ pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
|
|||||||
|
|
||||||
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
|
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
|
||||||
|
|
||||||
pin_verify[0] = 0x00; /* bTimerOut */
|
pin_verify[0] = 0x00; /* bTimeOut */
|
||||||
pin_verify[1] = 0x00; /* bTimerOut2 */
|
pin_verify[1] = 0x00; /* bTimeOut2 */
|
||||||
pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
|
pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
|
||||||
pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */
|
pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */
|
||||||
pin_verify[4] = 0x00; /* bmPINLengthFormat */
|
pin_verify[4] = 0x00; /* bmPINLengthFormat */
|
||||||
@ -2143,7 +2249,7 @@ pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
|
|||||||
pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */
|
pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */
|
||||||
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
|
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
|
||||||
pin_verify[7] |= 0x01; /* Max size reached. */
|
pin_verify[7] |= 0x01; /* Max size reached. */
|
||||||
pin_verify[8] = 0xff; /* bNumberMessage: Default */
|
pin_verify[8] = 0x01; /* bNumberMessage: One message */
|
||||||
pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */
|
pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */
|
||||||
pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */
|
pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */
|
||||||
pin_verify[11] = 0x00; /* bMsgIndex */
|
pin_verify[11] = 0x00; /* bMsgIndex */
|
||||||
@ -2219,8 +2325,8 @@ pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
|
|||||||
|
|
||||||
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
|
no_lc = (!pininfo->fixedlen && reader_table[slot].is_spr532);
|
||||||
|
|
||||||
pin_modify[0] = 0x00; /* bTimerOut */
|
pin_modify[0] = 0x00; /* bTimeOut */
|
||||||
pin_modify[1] = 0x00; /* bTimerOut2 */
|
pin_modify[1] = 0x00; /* bTimeOut2 */
|
||||||
pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
|
pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
|
||||||
pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */
|
pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */
|
||||||
pin_modify[4] = 0x00; /* bmPINLengthFormat */
|
pin_modify[4] = 0x00; /* bmPINLengthFormat */
|
||||||
@ -2238,12 +2344,12 @@ pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
|
|||||||
pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */
|
pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */
|
||||||
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
|
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
|
||||||
pin_modify[10] |= 0x01; /* Max size reached. */
|
pin_modify[10] |= 0x01; /* Max size reached. */
|
||||||
pin_modify[11] = 0xff; /* bNumberMessage: Default */
|
pin_modify[11] = 0x03; /* bNumberMessage: Three messages */
|
||||||
pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */
|
pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */
|
||||||
pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */
|
pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */
|
||||||
pin_modify[14] = 0x00; /* bMsgIndex1 */
|
pin_modify[14] = 0x00; /* bMsgIndex1 */
|
||||||
pin_modify[15] = 0x00; /* bMsgIndex2 */
|
pin_modify[15] = 0x01; /* bMsgIndex2 */
|
||||||
pin_modify[16] = 0x00; /* bMsgIndex3 */
|
pin_modify[16] = 0x02; /* bMsgIndex3 */
|
||||||
pin_modify[17] = 0x00; /* bTeoPrologue[0] */
|
pin_modify[17] = 0x00; /* bTeoPrologue[0] */
|
||||||
pin_modify[18] = 0x00; /* bTeoPrologue[1] */
|
pin_modify[18] = 0x00; /* bTeoPrologue[1] */
|
||||||
pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
|
pin_modify[19] = 2 * pininfo->fixedlen + 0x05 - no_lc; /* bTeoPrologue[2] */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user