mirror of
https://git.openldap.org/openldap/openldap.git
synced 2025-01-18 11:05:48 +08:00
ITS#1712, rewritten dn_openssl2ldap(). Added dnDCEnormalize(), used by
dn_openssl2ldap() and sasl_external_x509dn_convert. Fixed realm handling for foreign Kerberos realms embedded in usernames.
This commit is contained in:
parent
03679b7ce5
commit
1bbd51da77
@ -829,3 +829,40 @@ dnIsSuffix(
|
||||
/* compare */
|
||||
return( strcmp( dn->bv_val + d, suffix->bv_val ) == 0 );
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a DN from X.500 format into a normalized DN
|
||||
*/
|
||||
int
|
||||
dnDCEnormalize( char *dce, struct berval *out )
|
||||
{
|
||||
int rc;
|
||||
LDAPDN *dn = NULL;
|
||||
|
||||
out->bv_val = NULL;
|
||||
out->bv_len = 0;
|
||||
|
||||
rc = ldap_str2dn( dce, &dn, LDAP_DN_FORMAT_DCE );
|
||||
if ( rc != LDAP_SUCCESS )
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* Schema-aware rewrite
|
||||
*/
|
||||
if ( LDAPDN_rewrite( dn, 0 ) != LDAP_SUCCESS ) {
|
||||
ldap_dnfree( dn );
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Back to string representation
|
||||
*/
|
||||
rc = ldap_dn2bv( dn, out, LDAP_DN_FORMAT_LDAPV3 );
|
||||
|
||||
ldap_dnfree( dn );
|
||||
|
||||
if ( rc != LDAP_SUCCESS ) {
|
||||
rc = LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
@ -404,6 +404,8 @@ LDAP_SLAPD_F (void) build_new_dn LDAP_P((
|
||||
|
||||
LDAP_SLAPD_F (void) dnParent LDAP_P(( struct berval *dn, struct berval *pdn ));
|
||||
|
||||
LDAP_SLAPD_F (int) dnDCEnormalize LDAP_P(( char *dce, struct berval *out ));
|
||||
|
||||
/*
|
||||
* entry.c
|
||||
*/
|
||||
|
@ -137,11 +137,11 @@ slap_sasl_log(
|
||||
|
||||
static struct berval ext_bv = { sizeof("EXTERNAL")-1, "EXTERNAL" };
|
||||
|
||||
int slap_sasl_getdn( Connection *conn, char *id,
|
||||
int slap_sasl_getdn( Connection *conn, char *id, int len,
|
||||
char *user_realm, struct berval *dn, int flags )
|
||||
{
|
||||
char *c1;
|
||||
int rc, len, is_dn = 0;
|
||||
int rc, is_dn = 0;
|
||||
sasl_conn_t *ctx;
|
||||
struct berval dn2;
|
||||
|
||||
@ -166,7 +166,7 @@ int slap_sasl_getdn( Connection *conn, char *id,
|
||||
return( LDAP_SUCCESS );
|
||||
}
|
||||
ctx = conn->c_sasl_context;
|
||||
len = strlen( id );
|
||||
if ( len == 0 ) len = strlen( id );
|
||||
|
||||
/* An authcID needs to be converted to authzID form */
|
||||
if( flags & FLAG_GETDN_AUTHCID ) {
|
||||
@ -177,8 +177,7 @@ int slap_sasl_getdn( Connection *conn, char *id,
|
||||
{
|
||||
/* check SASL external for X.509 style DN and */
|
||||
/* convert to dn:<dn> form */
|
||||
dn->bv_val = ldap_dcedn2dn( id );
|
||||
dn->bv_len = strlen(dn->bv_val);
|
||||
dnDCEnormalize( id, dn );
|
||||
is_dn = SET_DN;
|
||||
|
||||
} else {
|
||||
@ -309,7 +308,7 @@ slap_sasl_checkpass(
|
||||
/* XXX do we need to check sasldb as well? */
|
||||
|
||||
/* XXX can we do both steps at once? */
|
||||
rc = slap_sasl_getdn( conn, (char *)username, NULL, &dn,
|
||||
rc = slap_sasl_getdn( conn, (char *)username, 0, NULL, &dn,
|
||||
FLAG_GETDN_AUTHCID | FLAG_GETDN_FINAL );
|
||||
if ( rc != LDAP_SUCCESS ) {
|
||||
sasl_seterror( sconn, 0, ldap_err2string( rc ) );
|
||||
@ -388,7 +387,7 @@ slap_sasl_canonicalize(
|
||||
in ? in : "<empty>" );
|
||||
#endif
|
||||
|
||||
rc = slap_sasl_getdn( conn, (char *)in, (char *)user_realm, &dn,
|
||||
rc = slap_sasl_getdn( conn, (char *)in, inlen, (char *)user_realm, &dn,
|
||||
(flags == SASL_CU_AUTHID) ? FLAG_GETDN_AUTHCID : FLAG_GETDN_AUTHZID );
|
||||
if ( rc != LDAP_SUCCESS ) {
|
||||
sasl_seterror( sconn, 0, ldap_err2string( rc ) );
|
||||
@ -441,7 +440,7 @@ slap_sasl_authorize(
|
||||
Connection *conn = (Connection *)context;
|
||||
struct berval authcDN, authzDN;
|
||||
char *realm;
|
||||
int rc, equal = 1;
|
||||
int rc, equal = 1, ext = 0;
|
||||
|
||||
#ifdef NEW_LOGGING
|
||||
LDAP_LOG(( "sasl", LDAP_LEVEL_ENTRY,
|
||||
@ -456,11 +455,19 @@ slap_sasl_authorize(
|
||||
if ( requested_user )
|
||||
equal = !strcmp( auth_identity, requested_user );
|
||||
|
||||
realm = strchr( auth_identity, '@' );
|
||||
if ( realm )
|
||||
*realm++ = '\0';
|
||||
/* If using SASL-EXTERNAL, don't modify the ID in any way */
|
||||
if ( conn->c_sasl_bind_mech.bv_len == ext_bv.bv_len
|
||||
&& ( strcasecmp( ext_bv.bv_val, conn->c_sasl_bind_mech.bv_val ) == 0 )
|
||||
&& auth_identity[0] == '/' ) {
|
||||
ext = 1;
|
||||
realm = NULL;
|
||||
} else {
|
||||
/* Else look for an embedded realm in the name */
|
||||
realm = strchr( auth_identity, '@' );
|
||||
if ( realm ) *realm++ = '\0';
|
||||
}
|
||||
|
||||
rc = slap_sasl_getdn( conn, auth_identity, realm ? realm : (char *)def_realm,
|
||||
rc = slap_sasl_getdn( conn, auth_identity, alen, realm ? realm : (char *)def_realm,
|
||||
&authcDN, FLAG_GETDN_AUTHCID );
|
||||
if ( realm )
|
||||
realm[-1] = '@';
|
||||
@ -480,11 +487,14 @@ slap_sasl_authorize(
|
||||
return SASL_OK;
|
||||
}
|
||||
|
||||
realm = strchr( requested_user, '@' );
|
||||
if ( realm )
|
||||
*realm++ = '\0';
|
||||
if ( ext ) {
|
||||
realm = NULL;
|
||||
} else {
|
||||
realm = strchr( requested_user, '@' );
|
||||
if ( realm ) *realm++ = '\0';
|
||||
}
|
||||
|
||||
rc = slap_sasl_getdn( conn, requested_user, realm ? realm : (char *)def_realm,
|
||||
rc = slap_sasl_getdn( conn, requested_user, rlen, realm ? realm : (char *)def_realm,
|
||||
&authzDN, FLAG_GETDN_AUTHZID );
|
||||
if ( realm )
|
||||
realm[-1] = '@';
|
||||
@ -537,15 +547,15 @@ slap_sasl_authorize(
|
||||
static int
|
||||
slap_sasl_authorize(
|
||||
void *context,
|
||||
const char *authcid,
|
||||
const char *authzid,
|
||||
char *authcid,
|
||||
char *authzid,
|
||||
const char **user,
|
||||
const char **errstr)
|
||||
{
|
||||
struct berval authcDN, authzDN;
|
||||
int rc;
|
||||
int rc, ext = 0;
|
||||
Connection *conn = context;
|
||||
char *realm;
|
||||
char *realm, *xrealm;
|
||||
|
||||
*user = NULL;
|
||||
|
||||
@ -579,7 +589,17 @@ slap_sasl_authorize(
|
||||
|
||||
/* Convert the identities to DN's. If no authzid was given, client will
|
||||
be bound as the DN matching their username */
|
||||
rc = slap_sasl_getdn( conn, (char *)authcid, realm, &authcDN, FLAG_GETDN_AUTHCID );
|
||||
if ( conn->c_sasl_bind_mech.bv_len == ext_bv.bv_len
|
||||
&& ( strcasecmp( ext_bv.bv_val, conn->c_sasl_bind_mech.bv_val ) == 0 )
|
||||
&& authcid[0] == '/' ) {
|
||||
ext = 1;
|
||||
xrealm = NULL;
|
||||
} else {
|
||||
xrealm = strchr( authcid, '@' );
|
||||
if ( xrealm ) *xrealm++ = '\0';
|
||||
}
|
||||
rc = slap_sasl_getdn( conn, (char *)authcid, 0, xrealm ? xrealm : realm, &authcDN, FLAG_GETDN_AUTHCID );
|
||||
if ( xrealm ) xrealm[-1] = '@';
|
||||
if( rc != LDAP_SUCCESS ) {
|
||||
*errstr = ldap_err2string( rc );
|
||||
return SASL_NOAUTHZ;
|
||||
@ -598,7 +618,14 @@ slap_sasl_authorize(
|
||||
*errstr = NULL;
|
||||
return SASL_OK;
|
||||
}
|
||||
rc = slap_sasl_getdn( conn, (char *)authzid, realm, &authzDN, FLAG_GETDN_AUTHZID );
|
||||
if ( ext ) {
|
||||
xrealm = NULL;
|
||||
} else {
|
||||
xrealm = strchr( authzid, '@' );
|
||||
if ( xrealm ) *xrealm++ = '\0';
|
||||
}
|
||||
rc = slap_sasl_getdn( conn, (char *)authzid, 0, xrealm ? xrealm : realm, &authzDN, FLAG_GETDN_AUTHZID );
|
||||
if ( xrealm ) xrealm[-1] = '@';
|
||||
if( rc != LDAP_SUCCESS ) {
|
||||
ch_free( authcDN.bv_val );
|
||||
*errstr = ldap_err2string( rc );
|
||||
@ -1088,7 +1115,7 @@ int slap_sasl_bind(
|
||||
NULL, "no SASL username", NULL, NULL );
|
||||
|
||||
} else {
|
||||
rc = slap_sasl_getdn( conn, username, realm, edn, FLAG_GETDN_FINAL );
|
||||
rc = slap_sasl_getdn( conn, username, 0, realm, edn, FLAG_GETDN_FINAL );
|
||||
|
||||
if( rc == LDAP_SUCCESS ) {
|
||||
sasl_ssf_t *ssf = NULL;
|
||||
|
@ -3421,7 +3421,7 @@ char digit[] = "0123456789";
|
||||
*/
|
||||
|
||||
static struct berval *
|
||||
asn1_integer2str(ASN1_INTEGER *a)
|
||||
asn1_integer2str(ASN1_INTEGER *a, struct berval *bv)
|
||||
{
|
||||
char buf[256];
|
||||
char *p;
|
||||
@ -3477,35 +3477,17 @@ asn1_integer2str(ASN1_INTEGER *a)
|
||||
*--p = '-';
|
||||
}
|
||||
|
||||
return ber_bvstrdup(p);
|
||||
return ber_str2bv( p, 0, 1, bv );
|
||||
}
|
||||
|
||||
/* Get a DN in RFC2253 format from a X509_NAME internal struct */
|
||||
static struct berval *
|
||||
dn_openssl2ldap(X509_NAME *name)
|
||||
int
|
||||
dn_openssl2ldap(X509_NAME *name, struct berval *out)
|
||||
{
|
||||
char issuer_dn[1024];
|
||||
BIO *bio;
|
||||
char buf[2048], *p;
|
||||
|
||||
bio = BIO_new(BIO_s_mem());
|
||||
if ( !bio ) {
|
||||
#ifdef NEW_LOGGING
|
||||
LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
|
||||
"dn_openssl2ldap: error creating BIO_s_mem: %s\n",
|
||||
ERR_error_string(ERR_get_error(),NULL)));
|
||||
#else
|
||||
Debug( LDAP_DEBUG_ARGS, "dn_openssl2ldap: "
|
||||
"error creating BIO: %s\n",
|
||||
ERR_error_string(ERR_get_error(),NULL), NULL, NULL );
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253);
|
||||
|
||||
BIO_gets(bio, issuer_dn, 1024);
|
||||
|
||||
BIO_free(bio);
|
||||
return ber_bvstrdup(issuer_dn);
|
||||
p = X509_NAME_oneline( name, buf, sizeof( buf ) );
|
||||
return dnDCEnormalize( p, out );
|
||||
}
|
||||
|
||||
/*
|
||||
@ -3519,9 +3501,8 @@ certificateExactConvert(
|
||||
{
|
||||
X509 *xcert;
|
||||
unsigned char *p = in->bv_val;
|
||||
struct berval *serial;
|
||||
struct berval *issuer_dn;
|
||||
struct berval *bv_tmp;
|
||||
struct berval serial;
|
||||
struct berval issuer_dn;
|
||||
|
||||
xcert = d2i_X509(NULL, &p, in->bv_len);
|
||||
if ( !xcert ) {
|
||||
@ -3537,39 +3518,27 @@ certificateExactConvert(
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
|
||||
serial = asn1_integer2str(xcert->cert_info->serialNumber);
|
||||
if ( !serial ) {
|
||||
if ( !asn1_integer2str(xcert->cert_info->serialNumber, &serial) ) {
|
||||
X509_free(xcert);
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
issuer_dn = dn_openssl2ldap(X509_get_issuer_name(xcert));
|
||||
if ( !issuer_dn ) {
|
||||
if ( dn_openssl2ldap(X509_get_issuer_name(xcert), &issuer_dn ) != LDAP_SUCCESS ) {
|
||||
X509_free(xcert);
|
||||
ber_bvfree(serial);
|
||||
ber_memfree(serial.bv_val);
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
/* Actually, dn_openssl2ldap returns in a normalized format, but
|
||||
it is different from our normalized format */
|
||||
bv_tmp = issuer_dn;
|
||||
if ( dnNormalize(NULL, bv_tmp, &issuer_dn) != LDAP_SUCCESS ) {
|
||||
X509_free(xcert);
|
||||
ber_bvfree(serial);
|
||||
ber_bvfree(bv_tmp);
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
ber_bvfree(bv_tmp);
|
||||
|
||||
X509_free(xcert);
|
||||
|
||||
out->bv_len = serial->bv_len + issuer_dn->bv_len + sizeof(" $ ");
|
||||
out->bv_len = serial.bv_len + issuer_dn.bv_len + sizeof(" $ ");
|
||||
out->bv_val = ch_malloc(out->bv_len);
|
||||
p = out->bv_val;
|
||||
AC_MEMCPY(p, serial->bv_val, serial->bv_len);
|
||||
p += serial->bv_len;
|
||||
AC_MEMCPY(p, serial.bv_val, serial.bv_len);
|
||||
p += serial.bv_len;
|
||||
AC_MEMCPY(p, " $ ", sizeof(" $ ")-1);
|
||||
p += 3;
|
||||
AC_MEMCPY(p, issuer_dn->bv_val, issuer_dn->bv_len);
|
||||
p += issuer_dn->bv_len;
|
||||
AC_MEMCPY(p, issuer_dn.bv_val, issuer_dn.bv_len);
|
||||
p += issuer_dn.bv_len;
|
||||
*p++ = '\0';
|
||||
|
||||
#ifdef NEW_LOGGING
|
||||
@ -3582,8 +3551,8 @@ certificateExactConvert(
|
||||
out->bv_val, NULL, NULL );
|
||||
#endif
|
||||
|
||||
ber_bvfree(serial);
|
||||
ber_bvfree(issuer_dn);
|
||||
ber_memfree(serial.bv_val);
|
||||
ber_memfree(issuer_dn.bv_val);
|
||||
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
@ -3591,8 +3560,8 @@ certificateExactConvert(
|
||||
static int
|
||||
serial_and_issuer_parse(
|
||||
struct berval *assertion,
|
||||
struct berval **serial,
|
||||
struct berval **issuer_dn
|
||||
struct berval *serial,
|
||||
struct berval *issuer_dn
|
||||
)
|
||||
{
|
||||
char *begin;
|
||||
@ -3617,18 +3586,20 @@ serial_and_issuer_parse(
|
||||
|
||||
bv.bv_len = end-begin+1;
|
||||
bv.bv_val = begin;
|
||||
*serial = ber_dupbv(NULL, &bv);
|
||||
ber_dupbv(serial, &bv);
|
||||
|
||||
/* now extract the issuer, remember p was at the dollar sign */
|
||||
begin = p+1;
|
||||
end = assertion->bv_val+assertion->bv_len-1;
|
||||
while (ASCII_SPACE(*begin))
|
||||
begin++;
|
||||
/* should we trim spaces at the end too? is it safe always? */
|
||||
if ( issuer_dn ) {
|
||||
begin = p+1;
|
||||
end = assertion->bv_val+assertion->bv_len-1;
|
||||
while (ASCII_SPACE(*begin))
|
||||
begin++;
|
||||
/* should we trim spaces at the end too? is it safe always? */
|
||||
|
||||
bv.bv_len = end-begin+1;
|
||||
bv.bv_val = begin;
|
||||
dnNormalize( NULL, &bv, issuer_dn );
|
||||
bv.bv_len = end-begin+1;
|
||||
bv.bv_val = begin;
|
||||
dnNormalize2( NULL, &bv, issuer_dn );
|
||||
}
|
||||
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
@ -3644,10 +3615,10 @@ certificateExactMatch(
|
||||
{
|
||||
X509 *xcert;
|
||||
unsigned char *p = value->bv_val;
|
||||
struct berval *serial;
|
||||
struct berval *issuer_dn;
|
||||
struct berval *asserted_serial;
|
||||
struct berval *asserted_issuer_dn;
|
||||
struct berval serial;
|
||||
struct berval issuer_dn;
|
||||
struct berval asserted_serial;
|
||||
struct berval asserted_issuer_dn;
|
||||
int ret;
|
||||
|
||||
xcert = d2i_X509(NULL, &p, value->bv_len);
|
||||
@ -3664,8 +3635,8 @@ certificateExactMatch(
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
|
||||
serial = asn1_integer2str(xcert->cert_info->serialNumber);
|
||||
issuer_dn = dn_openssl2ldap(X509_get_issuer_name(xcert));
|
||||
asn1_integer2str(xcert->cert_info->serialNumber, &serial);
|
||||
dn_openssl2ldap(X509_get_issuer_name(xcert), &issuer_dn);
|
||||
|
||||
X509_free(xcert);
|
||||
|
||||
@ -3678,8 +3649,8 @@ certificateExactMatch(
|
||||
flags,
|
||||
slap_schema.si_syn_integer,
|
||||
slap_schema.si_mr_integerMatch,
|
||||
serial,
|
||||
asserted_serial);
|
||||
&serial,
|
||||
&asserted_serial);
|
||||
if ( ret == LDAP_SUCCESS ) {
|
||||
if ( *matchp == 0 ) {
|
||||
/* We need to normalize everything for dnMatch */
|
||||
@ -3688,29 +3659,29 @@ certificateExactMatch(
|
||||
flags,
|
||||
slap_schema.si_syn_distinguishedName,
|
||||
slap_schema.si_mr_distinguishedNameMatch,
|
||||
issuer_dn,
|
||||
asserted_issuer_dn);
|
||||
&issuer_dn,
|
||||
&asserted_issuer_dn);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef NEW_LOGGING
|
||||
LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
|
||||
"certificateExactMatch: %d\n %s $ %s\n %s $ %s\n",
|
||||
*matchp, serial->bv_val, issuer_dn->bv_val,
|
||||
asserted_serial->bv_val, asserted_issuer_dn->bv_val));
|
||||
*matchp, serial.bv_val, issuer_dn.bv_val,
|
||||
asserted_serial.bv_val, asserted_issuer_dn.bv_val));
|
||||
#else
|
||||
Debug( LDAP_DEBUG_ARGS, "certificateExactMatch "
|
||||
"%d\n\t\"%s $ %s\"\n",
|
||||
*matchp, serial->bv_val, issuer_dn->bv_val );
|
||||
*matchp, serial.bv_val, issuer_dn.bv_val );
|
||||
Debug( LDAP_DEBUG_ARGS, "\t\"%s $ %s\"\n",
|
||||
asserted_serial->bv_val, asserted_issuer_dn->bv_val,
|
||||
asserted_serial.bv_val, asserted_issuer_dn.bv_val,
|
||||
NULL );
|
||||
#endif
|
||||
|
||||
ber_bvfree(serial);
|
||||
ber_bvfree(issuer_dn);
|
||||
ber_bvfree(asserted_serial);
|
||||
ber_bvfree(asserted_issuer_dn);
|
||||
ber_memfree(serial.bv_val);
|
||||
ber_memfree(issuer_dn.bv_val);
|
||||
ber_memfree(asserted_serial.bv_val);
|
||||
ber_memfree(asserted_issuer_dn.bv_val);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -3733,7 +3704,7 @@ static int certificateExactIndexer(
|
||||
BerVarray keys;
|
||||
X509 *xcert;
|
||||
unsigned char *p;
|
||||
struct berval * serial;
|
||||
struct berval serial;
|
||||
|
||||
/* we should have at least one value at this point */
|
||||
assert( values != NULL && values[0].bv_val != NULL );
|
||||
@ -3762,12 +3733,12 @@ static int certificateExactIndexer(
|
||||
return LDAP_INVALID_SYNTAX;
|
||||
}
|
||||
|
||||
serial = asn1_integer2str(xcert->cert_info->serialNumber);
|
||||
asn1_integer2str(xcert->cert_info->serialNumber, &serial);
|
||||
X509_free(xcert);
|
||||
integerNormalize( slap_schema.si_syn_integer,
|
||||
serial,
|
||||
&serial,
|
||||
&keys[i] );
|
||||
ber_bvfree(serial);
|
||||
ber_memfree(serial.bv_val);
|
||||
#ifdef NEW_LOGGING
|
||||
LDAP_LOG(( "schema", LDAP_LEVEL_ENTRY,
|
||||
"certificateExactIndexer: returning: %s\n",
|
||||
@ -3797,20 +3768,18 @@ static int certificateExactFilter(
|
||||
BerVarray *keysp )
|
||||
{
|
||||
BerVarray keys;
|
||||
struct berval *asserted_serial;
|
||||
struct berval *asserted_issuer_dn;
|
||||
struct berval asserted_serial;
|
||||
|
||||
serial_and_issuer_parse(assertValue,
|
||||
&asserted_serial,
|
||||
&asserted_issuer_dn);
|
||||
NULL);
|
||||
|
||||
keys = ch_malloc( sizeof( struct berval ) * 2 );
|
||||
integerNormalize( syntax, asserted_serial, &keys[0] );
|
||||
integerNormalize( syntax, &asserted_serial, &keys[0] );
|
||||
keys[1].bv_val = NULL;
|
||||
*keysp = keys;
|
||||
|
||||
ber_bvfree(asserted_serial);
|
||||
ber_bvfree(asserted_issuer_dn);
|
||||
ber_memfree(asserted_serial.bv_val);
|
||||
return LDAP_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user