X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5c19dc3ae3bd8e40a9c028b0deddd50ff337692c..07691282a056c4efea71e1e505527601e8cc166b:/OSX/libsecurity_smime/lib/cmssiginfo.c?ds=sidebyside diff --git a/OSX/libsecurity_smime/lib/cmssiginfo.c b/OSX/libsecurity_smime/lib/cmssiginfo.c index 29c3e1df..3c1ff754 100644 --- a/OSX/libsecurity_smime/lib/cmssiginfo.c +++ b/OSX/libsecurity_smime/lib/cmssiginfo.c @@ -53,13 +53,17 @@ #include #include #include +#include #include #include #include +#include #include "tsaSupport.h" #include "tsaSupportPriv.h" +#include + #define HIDIGIT(v) (((v) / 10) + '0') #define LODIGIT(v) (((v) % 10) + '0') @@ -211,12 +215,22 @@ SecCmsSignerInfoCreate(SecCmsMessageRef cmsg, SecIdentityRef identity, SECOidTag SecCmsSignerInfoRef signerInfo = NULL; SecCertificateRef cert = NULL; SecPrivateKeyRef signingKey = NULL; + CFDictionaryRef keyAttrs = NULL; if (SecIdentityCopyCertificate(identity, &cert)) goto loser; if (SecIdentityCopyPrivateKey(identity, &signingKey)) goto loser; + /* In some situations, the "Private Key" in the identity is actually a public key. */ + keyAttrs = SecKeyCopyAttributes(signingKey); + if (!keyAttrs) + goto loser; + CFTypeRef class = CFDictionaryGetValue(keyAttrs, kSecAttrKeyClass); + if (!class || (CFGetTypeID(class) != CFStringGetTypeID()) || !CFEqual(class, kSecAttrKeyClassPrivate)) + goto loser; + + signerInfo = nss_cmssignerinfo_create(cmsg, SecCmsSignerIDIssuerSN, cert, NULL, NULL, signingKey, digestalgtag); loser: @@ -224,6 +238,8 @@ loser: CFRelease(cert); if (signingKey) CFRelease(signingKey); + if (keyAttrs) + CFRelease(keyAttrs); return signerInfo; } @@ -265,8 +281,10 @@ nss_cmssignerinfo_create(SecCmsMessageRef cmsg, SecCmsSignerIDSelector type, Sec if (!subjKeyID) goto loser; signerinfo->signerIdentifier.id.subjectKeyID = PORT_ArenaNew(poolp, CSSM_DATA); - SECITEM_CopyItem(poolp, signerinfo->signerIdentifier.id.subjectKeyID, - subjKeyID); + if (SECITEM_CopyItem(poolp, signerinfo->signerIdentifier.id.subjectKeyID, + subjKeyID)) { + goto loser; + } signerinfo->pubKey = SECKEY_CopyPublicKey(pubKey); if (!signerinfo->pubKey) goto loser; @@ -321,6 +339,21 @@ SecCmsSignerInfoDestroy(SecCmsSignerInfoRef si) (int)CFGetRetainCount(si->timestampCertList)); CFRelease(si->timestampCertList); } + if (si->timestampCert != NULL) { + dprintfRC("SecCmsSignerInfoDestroy top: timestampCert.rc %d\n", + (int)CFGetRetainCount(si->timestampCert)); + CFRelease(si->timestampCert); + } + if (si->hashAgilityAttrValue != NULL) { + dprintfRC("SecCmsSignerInfoDestroy top: hashAgilityAttrValue.rc %d\n", + (int)CFGetRetainCount(si->hashAgilityAttrValue)); + CFRelease(si->hashAgilityAttrValue); + } + if (si->hashAgilityV2AttrValues != NULL) { + dprintfRC("SecCmsSignerInfoDestroy top: hashAgilityV2AttrValues.rc %d\n", + (int)CFGetRetainCount(si->hashAgilityV2AttrValues)); + CFRelease(si->hashAgilityV2AttrValues); + } /* XXX storage ??? */ } @@ -494,11 +527,6 @@ loser: SECITEM_FreeItem (&signature, PR_FALSE); if (privkey) SECKEY_DestroyPrivateKey(privkey); - if((algID != NULL) & (algID != &freeAlgID)) { - /* this is dicey - this was actually mallocd by either SecCertificate or - * by SecKey...it all boils down to a free() in the end though. */ - SECOID_DestroyAlgorithmID((SECAlgorithmID *)algID, PR_FALSE); - } if (tmppoolp) PORT_FreeArena(tmppoolp, PR_FALSE); return SECFailure; @@ -584,11 +612,11 @@ OSStatus SecCmsSignerInfoVerifyWithPolicy(SecCmsSignerInfoRef signerinfo,CFTypeRef timeStampPolicy, CSSM_DATA_PTR digest, CSSM_DATA_PTR contentType) { SecPublicKeyRef publickey = NULL; - SecCmsAttribute *attr; + SecCmsAttribute *attr = NULL; CSSM_DATA encoded_attrs; - SecCertificateRef cert; + SecCertificateRef cert = NULL; SecCmsVerificationStatus vs = SecCmsVSUnverified; - PLArenaPool *poolp; + PLArenaPool *poolp = NULL; SECOidTag digestAlgTag, digestEncAlgTag; if (signerinfo == NULL) @@ -605,8 +633,8 @@ SecCmsSignerInfoVerifyWithPolicy(SecCmsSignerInfoRef signerinfo,CFTypeRef timeSt dprintfRC("SecCmsSignerInfoVerify top: cert %p cert.rc %d\n", cert, (int)CFGetRetainCount(cert)); debugShowSigningCertificate(signerinfo); - - if (SecCertificateCopyPublicKey(cert, &publickey)) { + + if (NULL == (publickey = SecCertificateCopyKey(cert))) { vs = SecCmsVSProcessingError; goto loser; } @@ -714,8 +742,9 @@ SecCmsSignerInfoVerifyWithPolicy(SecCmsSignerInfoRef signerinfo,CFTypeRef timeSt dprintf("found an unAuthAttr\n"); OSStatus rux = SecCmsSignerInfoVerifyUnAuthAttrsWithPolicy(signerinfo,timeStampPolicy); dprintf("SecCmsSignerInfoVerifyUnAuthAttrs Status: %ld\n", (long)rux); - if (rux) + if (rux) { goto loser; + } } if (vs == SecCmsVSBadSignature) { @@ -791,6 +820,10 @@ SecCmsSignerInfoVerifyUnAuthAttrsWithPolicy(SecCmsSignerInfoRef signerinfo,CFTyp dprintf("found an id-ct-TSTInfo\n"); // Don't check the nonce in this case status = decodeTimeStampTokenWithPolicy(signerinfo, timeStampPolicy, (attr->values)[0], &signerinfo->encDigest, 0); + if (status != errSecSuccess) { + secerror("timestamp verification failed: %d", (int)status); + } + xit: return status; } @@ -836,12 +869,18 @@ SecCmsSignerInfoGetCertList(SecCmsSignerInfoRef signerinfo) CFArrayRef SecCmsSignerInfoGetTimestampCertList(SecCmsSignerInfoRef signerinfo) { - dprintfRC("SecCmsSignerInfoGetCertList: timestampCertList.rc %d\n", + dprintfRC("SecCmsSignerInfoGetTimestampCertList: timestampCertList.rc %d\n", (int)CFGetRetainCount(signerinfo->timestampCertList)); return signerinfo->timestampCertList; } - +SecCertificateRef +SecCmsSignerInfoGetTimestampSigningCert(SecCmsSignerInfoRef signerinfo) +{ + dprintfRC("SecCmsSignerInfoGetTimestampSigningCert: timestampCert.rc %d\n", + (int)CFGetRetainCount(signerinfo->timestampCert)); + return signerinfo->timestampCert; +} int SecCmsSignerInfoGetVersion(SecCmsSignerInfoRef signerinfo) @@ -914,6 +953,186 @@ xit: return status; } +/*! + @function + @abstract Return the data in the signed Codesigning Hash Agility attribute. + @param sinfo SignerInfo data for this signer, pointer to a CFDataRef for attribute value + @discussion Returns a CFDataRef containing the value of the attribute + @result A return value of errSecInternal is an error trying to look up the oid. + A status value of success with null result data indicates the attribute was not present. + */ +OSStatus +SecCmsSignerInfoGetAppleCodesigningHashAgility(SecCmsSignerInfoRef sinfo, CFDataRef *sdata) +{ + SecCmsAttribute *attr; + CSSM_DATA_PTR value; + + if (sinfo == NULL || sdata == NULL) + return paramErr; + + *sdata = NULL; + + if (sinfo->hashAgilityAttrValue != NULL) { + *sdata = sinfo->hashAgilityAttrValue; /* cached copy */ + return SECSuccess; + } + + attr = SecCmsAttributeArrayFindAttrByOidTag(sinfo->authAttr, SEC_OID_APPLE_HASH_AGILITY, PR_TRUE); + + /* attribute not found */ + if (attr == NULL || (value = SecCmsAttributeGetValue(attr)) == NULL) + return SECSuccess; + + sinfo->hashAgilityAttrValue = CFDataCreate(NULL, value->Data, value->Length); /* make cached copy */ + if (sinfo->hashAgilityAttrValue) { + *sdata = sinfo->hashAgilityAttrValue; + return SECSuccess; + } + return errSecAllocate; +} + +/* AgileHash ::= SEQUENCE { + hashType OBJECT IDENTIFIER, + hashValues OCTET STRING } + */ +typedef struct { + SecAsn1Item digestOID; + SecAsn1Item digestValue; +} CMSAppleAgileHash; + +static const SecAsn1Template CMSAppleAgileHashTemplate[] = { + { SEC_ASN1_SEQUENCE, + 0, NULL, sizeof(CMSAppleAgileHash) }, + { SEC_ASN1_OBJECT_ID, + offsetof(CMSAppleAgileHash, digestOID), }, + { SEC_ASN1_OCTET_STRING, + offsetof(CMSAppleAgileHash, digestValue), }, + { 0, } +}; + +static OSStatus CMSAddAgileHashToDictionary(CFMutableDictionaryRef dictionary, SecAsn1Item *DERAgileHash) { + PLArenaPool *tmppoolp = NULL; + OSStatus status = errSecSuccess; + CMSAppleAgileHash agileHash; + CFDataRef digestValue = NULL; + CFNumberRef digestTag = NULL; + + tmppoolp = PORT_NewArena(1024); + if (tmppoolp == NULL) { + return errSecAllocate; + } + + if ((status = SEC_ASN1DecodeItem(tmppoolp, &agileHash, CMSAppleAgileHashTemplate, DERAgileHash)) != errSecSuccess) { + goto loser; + } + + int64_t tag = SECOID_FindOIDTag(&agileHash.digestOID); + digestTag = CFNumberCreate(NULL, kCFNumberSInt64Type, &tag); + digestValue = CFDataCreate(NULL, agileHash.digestValue.Data, agileHash.digestValue.Length); + CFDictionaryAddValue(dictionary, digestTag, digestValue); + +loser: + CFReleaseNull(digestValue); + CFReleaseNull(digestTag); + if (tmppoolp) { + PORT_FreeArena(tmppoolp, PR_FALSE); + } + return status; +} + +/*! + @function + @abstract Return the data in the signed Codesigning Hash Agility V2 attribute. + @param sinfo SignerInfo data for this signer, pointer to a CFDictionaryRef for attribute values + @discussion Returns a CFDictionaryRef containing the values of the attribute + @result A return value of errSecInternal is an error trying to look up the oid. + A status value of success with null result data indicates the attribute was not present. + */ +OSStatus +SecCmsSignerInfoGetAppleCodesigningHashAgilityV2(SecCmsSignerInfoRef sinfo, CFDictionaryRef *sdict) +{ + SecCmsAttribute *attr; + + if (sinfo == NULL || sdict == NULL) { + return errSecParam; + } + + *sdict = NULL; + + if (sinfo->hashAgilityV2AttrValues != NULL) { + *sdict = sinfo->hashAgilityV2AttrValues; /* cached copy */ + return SECSuccess; + } + + attr = SecCmsAttributeArrayFindAttrByOidTag(sinfo->authAttr, SEC_OID_APPLE_HASH_AGILITY_V2, PR_TRUE); + + /* attribute not found */ + if (attr == NULL) { + return SECSuccess; + } + + /* attrValues SET OF AttributeValue + * AttributeValue ::= ANY + */ + CSSM_DATA_PTR *values = attr->values; + if (values == NULL) { /* There must be values */ + return errSecDecode; + } + + CFMutableDictionaryRef agileHashValues = CFDictionaryCreateMutable(NULL, SecCmsArrayCount((void **)values), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + while (*values != NULL) { + (void)CMSAddAgileHashToDictionary(agileHashValues, *values++); + } + if (CFDictionaryGetCount(agileHashValues) != SecCmsArrayCount((void **)attr->values)) { + CFReleaseNull(agileHashValues); + return errSecDecode; + } + + sinfo->hashAgilityV2AttrValues = agileHashValues; /* make cached copy */ + if (sinfo->hashAgilityV2AttrValues) { + *sdict = sinfo->hashAgilityV2AttrValues; + return SECSuccess; + } + return errSecAllocate; +} + +/* + * SecCmsSignerInfoGetAppleExpirationTime - return the expiration time, + * in UTCTime format, of a CMS signerInfo. + * + * sinfo - signerInfo data for this signer + * + * Returns a pointer to XXXX (what?) + * A return value of NULL is an error. + */ +OSStatus +SecCmsSignerInfoGetAppleExpirationTime(SecCmsSignerInfoRef sinfo, CFAbsoluteTime *etime) +{ + SecCmsAttribute *attr = NULL; + SecAsn1Item * value = NULL; + + if (sinfo == NULL || etime == NULL) { + return SECFailure; + } + + if (sinfo->expirationTime != 0) { + *etime = sinfo->expirationTime; /* cached copy */ + return SECSuccess; + } + + attr = SecCmsAttributeArrayFindAttrByOidTag(sinfo->authAttr, SEC_OID_APPLE_EXPIRATION_TIME, PR_TRUE); + if (attr == NULL || (value = SecCmsAttributeGetValue(attr)) == NULL) { + return SECFailure; + } + if (DER_UTCTimeToCFDate(value, etime) != SECSuccess) { + return SECFailure; + } + sinfo->expirationTime = *etime; /* make cached copy */ + return SECSuccess; +} + /* * Return the signing cert of a CMS signerInfo. * @@ -948,11 +1167,11 @@ SecCmsSignerInfoGetSigningCertificate(SecCmsSignerInfoRef signerinfo, SecKeychai sid = &signerinfo->signerIdentifier; switch (sid->identifierType) { case SecCmsSignerIDIssuerSN: - cert = CERT_FindCertByIssuerAndSN(keychainOrArray, rawCerts, signerinfo->cmsg->poolp, + cert = CERT_FindCertByIssuerAndSN(keychainOrArray, rawCerts, signerinfo->sigd->certs, signerinfo->cmsg->poolp, sid->id.issuerAndSN); break; case SecCmsSignerIDSubjectKeyID: - cert = CERT_FindCertBySubjectKeyID(keychainOrArray, rawCerts, sid->id.subjectKeyID); + cert = CERT_FindCertBySubjectKeyID(keychainOrArray, rawCerts, signerinfo->sigd->certs, sid->id.subjectKeyID); break; default: cert = NULL; @@ -985,7 +1204,9 @@ SecCmsSignerInfoGetSignerCommonName(SecCmsSignerInfoRef sinfo) if ((signercert = SecCmsSignerInfoGetSigningCertificate(sinfo, NULL)) == NULL) return NULL; - SecCertificateCopyCommonName(signercert, &commonName); + if (errSecSuccess != SecCertificateCopyCommonName(signercert, &commonName)) { + return NULL; + } return commonName; } @@ -1181,7 +1402,7 @@ loser: /* * SecCmsSignerInfoAddMSSMIMEEncKeyPrefs - add a SMIMEEncryptionKeyPreferences attribute to the - * authenticated (i.e. signed) attributes of "signerinfo", using the OID prefered by Microsoft. + * authenticated (i.e. signed) attributes of "signerinfo", using the OID preferred by Microsoft. * * This is expected to be included in outgoing signed messages for email (S/MIME), * if compatibility with Microsoft mail clients is wanted. @@ -1306,6 +1527,221 @@ SecCmsSignerInfoAddCounterSignature(SecCmsSignerInfoRef signerinfo, return SECFailure; } +/*! + @function + @abstract Add the Apple Codesigning Hash Agility attribute to the authenticated (i.e. signed) attributes of "signerinfo". + @discussion This is expected to be included in outgoing Apple code signatures. + */ +OSStatus +SecCmsSignerInfoAddAppleCodesigningHashAgility(SecCmsSignerInfoRef signerinfo, CFDataRef attrValue) +{ + SecCmsAttribute *attr; + PLArenaPool *poolp = signerinfo->cmsg->poolp; + void *mark = PORT_ArenaMark(poolp); + OSStatus status = SECFailure; + + /* The value is required for this attribute. */ + if (!attrValue) { + status = errSecParam; + goto loser; + } + + /* + * SecCmsAttributeCreate makes a copy of the data in value, so + * we don't need to copy into the CSSM_DATA struct. + */ + CSSM_DATA value; + value.Length = CFDataGetLength(attrValue); + value.Data = (uint8_t *)CFDataGetBytePtr(attrValue); + + if ((attr = SecCmsAttributeCreate(poolp, + SEC_OID_APPLE_HASH_AGILITY, + &value, + PR_FALSE)) == NULL) { + status = errSecAllocate; + goto loser; + } + + if (SecCmsSignerInfoAddAuthAttr(signerinfo, attr) != SECSuccess) { + status = errSecInternalError; + goto loser; + } + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return status; +} + +static OSStatus CMSAddAgileHashToAttribute(PLArenaPool *poolp, SecCmsAttribute *attr, CFNumberRef cftag, CFDataRef value) { + PLArenaPool *tmppoolp = NULL; + int64_t tag; + SECOidData *digestOid = NULL; + CMSAppleAgileHash agileHash; + SecAsn1Item attrValue = { .Data = NULL, .Length = 0 }; + OSStatus status = errSecSuccess; + + memset(&agileHash, 0, sizeof(agileHash)); + + if(!CFNumberGetValue(cftag, kCFNumberSInt64Type, &tag)) { + return errSecParam; + } + digestOid = SECOID_FindOIDByTag((SECOidTag)tag); + + agileHash.digestValue.Data = (uint8_t *)CFDataGetBytePtr(value); + agileHash.digestValue.Length = CFDataGetLength(value); + agileHash.digestOID.Data = digestOid->oid.Data; + agileHash.digestOID.Length = digestOid->oid.Length; + + tmppoolp = PORT_NewArena(1024); + if (tmppoolp == NULL) { + return errSecAllocate; + } + + if (SEC_ASN1EncodeItem(tmppoolp, &attrValue, &agileHash, CMSAppleAgileHashTemplate) == NULL) { + status = errSecParam; + goto loser; + } + + status = SecCmsAttributeAddValue(poolp, attr, &attrValue); + +loser: + if (tmppoolp) { + PORT_FreeArena(tmppoolp, PR_FALSE); + } + return status; +} + +/*! + @function + @abstract Add the Apple Codesigning Hash Agility attribute to the authenticated (i.e. signed) attributes of "signerinfo". + @discussion This is expected to be included in outgoing Apple code signatures. + */ +OSStatus +SecCmsSignerInfoAddAppleCodesigningHashAgilityV2(SecCmsSignerInfoRef signerinfo, CFDictionaryRef attrValues) +{ + __block SecCmsAttribute *attr; + __block PLArenaPool *poolp = signerinfo->cmsg->poolp; + void *mark = PORT_ArenaMark(poolp); + OSStatus status = SECFailure; + + /* The value is required for this attribute. */ + if (!attrValues) { + status = errSecParam; + goto loser; + } + + if ((attr = SecCmsAttributeCreate(poolp, SEC_OID_APPLE_HASH_AGILITY_V2, + NULL, PR_TRUE)) == NULL) { + status = errSecAllocate; + goto loser; + } + + CFDictionaryForEach(attrValues, ^(const void *key, const void *value) { + if (!isNumber(key) || !isData(value)) { + return; + } + (void)CMSAddAgileHashToAttribute(poolp, attr, (CFNumberRef)key, (CFDataRef)value); + }); + + if (SecCmsSignerInfoAddAuthAttr(signerinfo, attr) != SECSuccess) { + status = errSecInternal; + goto loser; + } + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return status; +} + +/* + * SecCmsSignerInfoAddAppleExpirationTime - add the expiration time to the + * authenticated (i.e. signed) attributes of "signerinfo". + * + * This is expected to be included in outgoing signed + * messages for Asset Receipts but is likely useful in other situations. + * + * This should only be added once; a second call will do nothing. + */ +OSStatus +SecCmsSignerInfoAddAppleExpirationTime(SecCmsSignerInfoRef signerinfo, CFAbsoluteTime t) +{ + SecCmsAttribute *attr = NULL; + PLArenaPool *poolp = signerinfo->cmsg->poolp; + void *mark = PORT_ArenaMark(poolp); + + /* create new expiration time attribute */ + SecAsn1Item etime; + if (DER_CFDateToUTCTime(t, &etime) != SECSuccess) { + goto loser; + } + + if ((attr = SecCmsAttributeCreate(poolp, SEC_OID_APPLE_EXPIRATION_TIME, &etime, PR_FALSE)) == NULL) { + SECITEM_FreeItem (&etime, PR_FALSE); + goto loser; + } + + SECITEM_FreeItem(&etime, PR_FALSE); + + if (SecCmsSignerInfoAddAuthAttr(signerinfo, attr) != SECSuccess) { + goto loser; + } + + PORT_ArenaUnmark(poolp, mark); + return SECSuccess; + +loser: + PORT_ArenaRelease(poolp, mark); + return SECFailure; +} + +SecCertificateRef SecCmsSignerInfoCopyCertFromEncryptionKeyPreference(SecCmsSignerInfoRef signerinfo) { + SecCertificateRef cert = NULL; + SecCmsAttribute *attr; + CSSM_DATA_PTR ekp; + SecKeychainRef keychainOrArray; + + (void)SecKeychainCopyDefault(&keychainOrArray); + + /* sanity check - see if verification status is ok (unverified does not count...) */ + if (signerinfo->verificationStatus != SecCmsVSGoodSignature) + return NULL; + + /* Prep the raw certs */ + CSSM_DATA_PTR *rawCerts = NULL; + if (signerinfo->sigd) { + rawCerts = signerinfo->sigd->rawCerts; + } + + /* find preferred encryption cert */ + if (!SecCmsArrayIsEmpty((void **)signerinfo->authAttr) && + (attr = SecCmsAttributeArrayFindAttrByOidTag(signerinfo->authAttr, + SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE, PR_TRUE)) != NULL) + { /* we have a SMIME_ENCRYPTION_KEY_PREFERENCE attribute! Find the cert. */ + ekp = SecCmsAttributeGetValue(attr); + if (ekp == NULL) + return NULL; + cert = SecSMIMEGetCertFromEncryptionKeyPreference(keychainOrArray, rawCerts, ekp); + } + if(cert) return cert; + + if (!SecCmsArrayIsEmpty((void **)signerinfo->authAttr) && + (attr = SecCmsAttributeArrayFindAttrByOidTag(signerinfo->authAttr, + SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE, PR_TRUE)) != NULL) + { /* we have a MS_SMIME_ENCRYPTION_KEY_PREFERENCE attribute! Find the cert. */ + ekp = SecCmsAttributeGetValue(attr); + if (ekp == NULL) + return NULL; + cert = SecSMIMEGetCertFromEncryptionKeyPreference(keychainOrArray, rawCerts, ekp); + } + return cert; +} + /* * XXXX the following needs to be done in the S/MIME layer code * after signature of a signerinfo is verified @@ -1341,7 +1777,7 @@ SecCmsSignerInfoSaveSMIMEProfile(SecCmsSignerInfoRef signerinfo) /* we assume that all certs coming with the message have been imported to the */ /* temporary database */ - cert = SecSMIMEGetCertFromEncryptionKeyPreference(keychainOrArray, ekp); + cert = SecSMIMEGetCertFromEncryptionKeyPreference(keychainOrArray, NULL, ekp); if (cert == NULL) return SECFailure; must_free_cert = PR_TRUE;