FreeRDP
Loading...
Searching...
No Matches
schannel.c
1
20#include <winpr/config.h>
21
22#include <winpr/crt.h>
23#include <winpr/sspi.h>
24
25#include "schannel.h"
26
27#include "../sspi.h"
28#include "../../log.h"
29
30static char SCHANNEL_PACKAGE_NAME_A[] = "Schannel";
31static WCHAR SCHANNEL_PACKAGE_NAME_W[] = { W('S'), W('c'), W('h'), W('a'), W('n'),
32 W('n'), W('e'), W('l'), W('\0') };
33
34#define TAG WINPR_TAG("sspi.Schannel")
35
36SCHANNEL_CONTEXT* schannel_ContextNew(void)
37{
38 SCHANNEL_CONTEXT* context = nullptr;
39 context = (SCHANNEL_CONTEXT*)calloc(1, sizeof(SCHANNEL_CONTEXT));
40
41 if (!context)
42 return nullptr;
43
44 context->openssl = schannel_openssl_new();
45
46 if (!context->openssl)
47 {
48 free(context);
49 return nullptr;
50 }
51
52 return context;
53}
54
55void schannel_ContextFree(SCHANNEL_CONTEXT* context)
56{
57 if (!context)
58 return;
59
60 schannel_openssl_free(context->openssl);
61 free(context);
62}
63
64static SCHANNEL_CREDENTIALS* schannel_CredentialsNew(void)
65{
66 SCHANNEL_CREDENTIALS* credentials = nullptr;
67 credentials = (SCHANNEL_CREDENTIALS*)calloc(1, sizeof(SCHANNEL_CREDENTIALS));
68 return credentials;
69}
70
71static void schannel_CredentialsFree(SCHANNEL_CREDENTIALS* credentials)
72{
73 free(credentials);
74}
75
76static ALG_ID schannel_SupportedAlgs[] = { CALG_AES_128,
77 CALG_AES_256,
78 CALG_RC4,
79 CALG_DES,
80 CALG_3DES,
81 CALG_MD5,
82 CALG_SHA1,
83 CALG_SHA_256,
84 CALG_SHA_384,
85 CALG_SHA_512,
86 CALG_RSA_SIGN,
87 CALG_DH_EPHEM,
88 (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RESERVED7 |
89 6), /* what is this? */
90 CALG_DSS_SIGN,
91 CALG_ECDSA };
92
93static SECURITY_STATUS SEC_ENTRY schannel_QueryCredentialsAttributesW(
94 WINPR_ATTR_UNUSED PCredHandle phCredential, ULONG ulAttribute, void* pBuffer)
95{
96 if (ulAttribute == SECPKG_ATTR_SUPPORTED_ALGS)
97 {
99 SupportedAlgs->cSupportedAlgs = sizeof(schannel_SupportedAlgs) / sizeof(ALG_ID);
100 SupportedAlgs->palgSupportedAlgs = (ALG_ID*)schannel_SupportedAlgs;
101 return SEC_E_OK;
102 }
103 else if (ulAttribute == SECPKG_ATTR_CIPHER_STRENGTHS)
104 {
106 CipherStrengths->dwMinimumCipherStrength = 40;
107 CipherStrengths->dwMaximumCipherStrength = 256;
108 return SEC_E_OK;
109 }
110 else if (ulAttribute == SECPKG_ATTR_SUPPORTED_PROTOCOLS)
111 {
113 /* Observed SupportedProtocols: 0x208A0 */
114 SupportedProtocols->grbitProtocol = (SP_PROT_CLIENTS | SP_PROT_SERVERS);
115 return SEC_E_OK;
116 }
117
118 WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
119 return SEC_E_UNSUPPORTED_FUNCTION;
120}
121
122static SECURITY_STATUS SEC_ENTRY schannel_QueryCredentialsAttributesA(PCredHandle phCredential,
123 ULONG ulAttribute,
124 void* pBuffer)
125{
126 return schannel_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer);
127}
128
129static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleX(
130 void* name, ULONG fCredentialUse, WINPR_ATTR_UNUSED void* pvLogonID, void* pAuthData,
131 WINPR_ATTR_UNUSED SEC_GET_KEY_FN pGetKeyFn, WINPR_ATTR_UNUSED void* pvGetKeyArgument,
132 PCredHandle phCredential, WINPR_ATTR_UNUSED PTimeStamp ptsExpiry)
133{
134 WINPR_ASSERT(name);
135
136 if (fCredentialUse == SECPKG_CRED_OUTBOUND)
137 {
138 SCHANNEL_CREDENTIALS* credentials = schannel_CredentialsNew();
139 if (!credentials)
140 return SEC_E_INSUFFICIENT_MEMORY;
141
142 credentials->fCredentialUse = fCredentialUse;
143 SCHANNEL_CRED* cred = (SCHANNEL_CRED*)pAuthData;
144
145 if (cred)
146 CopyMemory(&credentials->cred, cred, sizeof(SCHANNEL_CRED));
147
148 sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
149 sspi_SecureHandleSetUpperPointer(phCredential, name);
150 return SEC_E_OK;
151 }
152 else if (fCredentialUse == SECPKG_CRED_INBOUND)
153 {
154 SCHANNEL_CREDENTIALS* credentials = schannel_CredentialsNew();
155 if (!credentials)
156 return SEC_E_INSUFFICIENT_MEMORY;
157
158 credentials->fCredentialUse = fCredentialUse;
159 sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials);
160 sspi_SecureHandleSetUpperPointer(phCredential, name);
161 return SEC_E_OK;
162 }
163 else
164 WLog_WARN(TAG, "Unsupported fCredentialUse=0x%08" PRIx32, fCredentialUse);
165
166 return SEC_E_OK;
167}
168
169// NOLINTBEGIN(readability-non-const-parameter)
170static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleA(
171 SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID,
172 void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential,
173 PTimeStamp ptsExpiry)
174{
175 WINPR_UNUSED(pszPrincipal);
176 WINPR_UNUSED(pszPackage);
177 return schannel_AcquireCredentialsHandleX(SCHANNEL_PACKAGE_NAME_A, fCredentialUse, pvLogonID,
178 pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential,
179 ptsExpiry);
180}
181
182static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleW(
183 WINPR_ATTR_UNUSED SEC_WCHAR* pszPrincipal, WINPR_ATTR_UNUSED SEC_WCHAR* pszPackage,
184 ULONG fCredentialUse, WINPR_ATTR_UNUSED void* pvLogonID, void* pAuthData,
185 WINPR_ATTR_UNUSED SEC_GET_KEY_FN pGetKeyFn, WINPR_ATTR_UNUSED void* pvGetKeyArgument,
186 PCredHandle phCredential, WINPR_ATTR_UNUSED PTimeStamp ptsExpiry)
187// NOLINTEND(readability-non-const-parameter)
188{
189 WINPR_UNUSED(pszPrincipal);
190 WINPR_UNUSED(pszPackage);
191 return schannel_AcquireCredentialsHandleX(SCHANNEL_PACKAGE_NAME_W, fCredentialUse, pvLogonID,
192 pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential,
193 ptsExpiry);
194}
195
196static SECURITY_STATUS SEC_ENTRY schannel_FreeCredentialsHandle(PCredHandle phCredential)
197{
198 if (!phCredential)
199 return SEC_E_INVALID_HANDLE;
200
201 SCHANNEL_CREDENTIALS* credentials =
202 (SCHANNEL_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
203 sspi_SecureHandleInvalidate(phCredential);
204 if (!credentials)
205 return SEC_E_INVALID_HANDLE;
206
207 schannel_CredentialsFree(credentials);
208 return SEC_E_OK;
209}
210
211static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextX(
212 void* name, PCredHandle phCredential, PCtxtHandle phContext,
213 WINPR_ATTR_UNUSED SEC_WCHAR* pszTargetName, WINPR_ATTR_UNUSED ULONG fContextReq,
214 WINPR_ATTR_UNUSED ULONG Reserved1, WINPR_ATTR_UNUSED ULONG TargetDataRep, PSecBufferDesc pInput,
215 WINPR_ATTR_UNUSED ULONG Reserved2, PCtxtHandle phNewContext, PSecBufferDesc pOutput,
216 WINPR_ATTR_UNUSED PULONG pfContextAttr, WINPR_ATTR_UNUSED PTimeStamp ptsExpiry)
217{
218 SECURITY_STATUS status = 0;
219 SCHANNEL_CONTEXT* context = nullptr;
220 SCHANNEL_CREDENTIALS* credentials = nullptr;
221
222 /* behave like windows SSPIs that don't want empty context */
223 if (phContext && !phContext->dwLower && !phContext->dwUpper)
224 return SEC_E_INVALID_HANDLE;
225
226 context = sspi_SecureHandleGetLowerPointer(phContext);
227
228 if (!context)
229 {
230 context = schannel_ContextNew();
231
232 if (!context)
233 return SEC_E_INSUFFICIENT_MEMORY;
234
235 credentials = (SCHANNEL_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential);
236 context->server = FALSE;
237 CopyMemory(&context->cred, &credentials->cred, sizeof(SCHANNEL_CRED));
238 sspi_SecureHandleSetLowerPointer(phNewContext, context);
239 sspi_SecureHandleSetUpperPointer(phNewContext, name);
240 schannel_openssl_client_init(context->openssl);
241 }
242
243 status = schannel_openssl_client_process_tokens(context->openssl, pInput, pOutput);
244 return status;
245}
246
247static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextW(
248 PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq,
249 ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
250 PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
251{
252 return schannel_InitializeSecurityContextX(
253 SCHANNEL_PACKAGE_NAME_W, phCredential, phContext, pszTargetName, fContextReq, Reserved1,
254 TargetDataRep, pInput, Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
255}
256
257static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextA(
258 PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq,
259 ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2,
260 PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry)
261{
262 SECURITY_STATUS status = 0;
263 SEC_WCHAR* pszTargetNameW = nullptr;
264
265 if (pszTargetName != nullptr)
266 {
267 pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, nullptr);
268 if (!pszTargetNameW)
269 return SEC_E_INSUFFICIENT_MEMORY;
270 }
271
272 status = schannel_InitializeSecurityContextX(
273 SCHANNEL_PACKAGE_NAME_A, phCredential, phContext, pszTargetNameW, fContextReq, Reserved1,
274 TargetDataRep, pInput, Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry);
275 free(pszTargetNameW);
276 return status;
277}
278
279static SECURITY_STATUS SEC_ENTRY schannel_AcceptSecurityContextA(
280 WINPR_ATTR_UNUSED PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput,
281 WINPR_ATTR_UNUSED ULONG fContextReq, WINPR_ATTR_UNUSED ULONG TargetDataRep,
282 PCtxtHandle phNewContext, PSecBufferDesc pOutput, WINPR_ATTR_UNUSED PULONG pfContextAttr,
283 WINPR_ATTR_UNUSED PTimeStamp ptsTimeStamp)
284{
285 SECURITY_STATUS status = 0;
286 SCHANNEL_CONTEXT* context = nullptr;
287
288 /* behave like windows SSPIs that don't want empty context */
289 if (phContext && !phContext->dwLower && !phContext->dwUpper)
290 return SEC_E_INVALID_HANDLE;
291
292 context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
293
294 if (!context)
295 {
296 context = schannel_ContextNew();
297
298 if (!context)
299 return SEC_E_INSUFFICIENT_MEMORY;
300
301 context->server = TRUE;
302 sspi_SecureHandleSetLowerPointer(phNewContext, context);
303 sspi_SecureHandleSetUpperPointer(phNewContext, (void*)SCHANNEL_PACKAGE_NAME_A);
304 schannel_openssl_server_init(context->openssl);
305 }
306
307 status = schannel_openssl_server_process_tokens(context->openssl, pInput, pOutput);
308 return status;
309}
310
311static SECURITY_STATUS SEC_ENTRY schannel_AcceptSecurityContextW(
312 WINPR_ATTR_UNUSED PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput,
313 WINPR_ATTR_UNUSED ULONG fContextReq, WINPR_ATTR_UNUSED ULONG TargetDataRep,
314 PCtxtHandle phNewContext, PSecBufferDesc pOutput, WINPR_ATTR_UNUSED PULONG pfContextAttr,
315 WINPR_ATTR_UNUSED PTimeStamp ptsTimeStamp)
316{
317 SECURITY_STATUS status = 0;
318 SCHANNEL_CONTEXT* context = nullptr;
319
320 /* behave like windows SSPIs that don't want empty context */
321 if (phContext && !phContext->dwLower && !phContext->dwUpper)
322 return SEC_E_INVALID_HANDLE;
323
324 context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
325
326 if (!context)
327 {
328 context = schannel_ContextNew();
329
330 if (!context)
331 return SEC_E_INSUFFICIENT_MEMORY;
332
333 context->server = TRUE;
334 sspi_SecureHandleSetLowerPointer(phNewContext, context);
335 sspi_SecureHandleSetUpperPointer(phNewContext, (void*)SCHANNEL_PACKAGE_NAME_W);
336 schannel_openssl_server_init(context->openssl);
337 }
338
339 status = schannel_openssl_server_process_tokens(context->openssl, pInput, pOutput);
340 return status;
341}
342
343static SECURITY_STATUS SEC_ENTRY schannel_DeleteSecurityContext(PCtxtHandle phContext)
344{
345 SCHANNEL_CONTEXT* context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
346 sspi_SecureHandleInvalidate(phContext);
347
348 if (!context)
349 return SEC_E_INVALID_HANDLE;
350
351 schannel_ContextFree(context);
352 return SEC_E_OK;
353}
354
355static SECURITY_STATUS SEC_ENTRY schannel_QueryContextAttributes(PCtxtHandle phContext,
356 ULONG ulAttribute, void* pBuffer)
357{
358 if (!phContext)
359 return SEC_E_INVALID_HANDLE;
360
361 if (!pBuffer)
362 return SEC_E_INSUFFICIENT_MEMORY;
363
364 if (ulAttribute == SECPKG_ATTR_SIZES)
365 {
366 SecPkgContext_Sizes* Sizes = (SecPkgContext_Sizes*)pBuffer;
367 Sizes->cbMaxToken = 0x6000;
368 Sizes->cbMaxSignature = 16;
369 Sizes->cbBlockSize = 0;
370 Sizes->cbSecurityTrailer = 16;
371 return SEC_E_OK;
372 }
373 else if (ulAttribute == SECPKG_ATTR_STREAM_SIZES)
374 {
376 StreamSizes->cbHeader = 5;
377 StreamSizes->cbTrailer = 36;
378 StreamSizes->cbMaximumMessage = 0x4000;
379 StreamSizes->cBuffers = 4;
380 StreamSizes->cbBlockSize = 16;
381 return SEC_E_OK;
382 }
383
384 WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute);
385 return SEC_E_UNSUPPORTED_FUNCTION;
386}
387
388static SECURITY_STATUS SEC_ENTRY schannel_MakeSignature(WINPR_ATTR_UNUSED PCtxtHandle phContext,
389 WINPR_ATTR_UNUSED ULONG fQOP,
390 WINPR_ATTR_UNUSED PSecBufferDesc pMessage,
391 WINPR_ATTR_UNUSED ULONG MessageSeqNo)
392{
393 return SEC_E_OK;
394}
395
396static SECURITY_STATUS SEC_ENTRY schannel_VerifySignature(WINPR_ATTR_UNUSED PCtxtHandle phContext,
397 WINPR_ATTR_UNUSED PSecBufferDesc pMessage,
398 WINPR_ATTR_UNUSED ULONG MessageSeqNo,
399 WINPR_ATTR_UNUSED ULONG* pfQOP)
400{
401 return SEC_E_OK;
402}
403
404static SECURITY_STATUS SEC_ENTRY schannel_EncryptMessage(WINPR_ATTR_UNUSED PCtxtHandle phContext,
405 WINPR_ATTR_UNUSED ULONG fQOP,
406 PSecBufferDesc pMessage,
407 WINPR_ATTR_UNUSED ULONG MessageSeqNo)
408{
409 SECURITY_STATUS status = 0;
410 SCHANNEL_CONTEXT* context = nullptr;
411 context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
412
413 if (!context)
414 return SEC_E_INVALID_HANDLE;
415
416 status = schannel_openssl_encrypt_message(context->openssl, pMessage);
417 return status;
418}
419
420static SECURITY_STATUS SEC_ENTRY schannel_DecryptMessage(PCtxtHandle phContext,
421 PSecBufferDesc pMessage,
422 WINPR_ATTR_UNUSED ULONG MessageSeqNo,
423 WINPR_ATTR_UNUSED ULONG* pfQOP)
424{
425 SECURITY_STATUS status = 0;
426 SCHANNEL_CONTEXT* context = nullptr;
427 context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext);
428
429 if (!context)
430 return SEC_E_INVALID_HANDLE;
431
432 status = schannel_openssl_decrypt_message(context->openssl, pMessage);
433 return status;
434}
435
436const SecurityFunctionTableA SCHANNEL_SecurityFunctionTableA = {
437 3, /* dwVersion */
438 nullptr, /* EnumerateSecurityPackages */
439 schannel_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */
440 schannel_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */
441 schannel_FreeCredentialsHandle, /* FreeCredentialsHandle */
442 nullptr, /* Reserved2 */
443 schannel_InitializeSecurityContextA, /* InitializeSecurityContext */
444 schannel_AcceptSecurityContextA, /* AcceptSecurityContext */
445 nullptr, /* CompleteAuthToken */
446 schannel_DeleteSecurityContext, /* DeleteSecurityContext */
447 nullptr, /* ApplyControlToken */
448 schannel_QueryContextAttributes, /* QueryContextAttributes */
449 nullptr, /* ImpersonateSecurityContext */
450 nullptr, /* RevertSecurityContext */
451 schannel_MakeSignature, /* MakeSignature */
452 schannel_VerifySignature, /* VerifySignature */
453 nullptr, /* FreeContextBuffer */
454 nullptr, /* QuerySecurityPackageInfo */
455 nullptr, /* Reserved3 */
456 nullptr, /* Reserved4 */
457 nullptr, /* ExportSecurityContext */
458 nullptr, /* ImportSecurityContext */
459 nullptr, /* AddCredentials */
460 nullptr, /* Reserved8 */
461 nullptr, /* QuerySecurityContextToken */
462 schannel_EncryptMessage, /* EncryptMessage */
463 schannel_DecryptMessage, /* DecryptMessage */
464 nullptr, /* SetContextAttributes */
465 nullptr, /* SetCredentialsAttributes */
466};
467
468const SecurityFunctionTableW SCHANNEL_SecurityFunctionTableW = {
469 3, /* dwVersion */
470 nullptr, /* EnumerateSecurityPackages */
471 schannel_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */
472 schannel_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */
473 schannel_FreeCredentialsHandle, /* FreeCredentialsHandle */
474 nullptr, /* Reserved2 */
475 schannel_InitializeSecurityContextW, /* InitializeSecurityContext */
476 schannel_AcceptSecurityContextW, /* AcceptSecurityContext */
477 nullptr, /* CompleteAuthToken */
478 schannel_DeleteSecurityContext, /* DeleteSecurityContext */
479 nullptr, /* ApplyControlToken */
480 schannel_QueryContextAttributes, /* QueryContextAttributes */
481 nullptr, /* ImpersonateSecurityContext */
482 nullptr, /* RevertSecurityContext */
483 schannel_MakeSignature, /* MakeSignature */
484 schannel_VerifySignature, /* VerifySignature */
485 nullptr, /* FreeContextBuffer */
486 nullptr, /* QuerySecurityPackageInfo */
487 nullptr, /* Reserved3 */
488 nullptr, /* Reserved4 */
489 nullptr, /* ExportSecurityContext */
490 nullptr, /* ImportSecurityContext */
491 nullptr, /* AddCredentials */
492 nullptr, /* Reserved8 */
493 nullptr, /* QuerySecurityContextToken */
494 schannel_EncryptMessage, /* EncryptMessage */
495 schannel_DecryptMessage, /* DecryptMessage */
496 nullptr, /* SetContextAttributes */
497 nullptr, /* SetCredentialsAttributes */
498};
499
500const SecPkgInfoA SCHANNEL_SecPkgInfoA = {
501 0x000107B3, /* fCapabilities */
502 1, /* wVersion */
503 0x000E, /* wRPCID */
504 SCHANNEL_CB_MAX_TOKEN, /* cbMaxToken */
505 "Schannel", /* Name */
506 "Schannel Security Package" /* Comment */
507};
508
509static WCHAR SCHANNEL_SecPkgInfoW_NameBuffer[32] = WINPR_C_ARRAY_INIT;
510static WCHAR SCHANNEL_SecPkgInfoW_CommentBuffer[32] = WINPR_C_ARRAY_INIT;
511
512const SecPkgInfoW SCHANNEL_SecPkgInfoW = {
513 0x000107B3, /* fCapabilities */
514 1, /* wVersion */
515 0x000E, /* wRPCID */
516 SCHANNEL_CB_MAX_TOKEN, /* cbMaxToken */
517 SCHANNEL_SecPkgInfoW_NameBuffer, /* Name */
518 SCHANNEL_SecPkgInfoW_CommentBuffer /* Comment */
519};
520
521BOOL SCHANNEL_init(void)
522{
523 InitializeConstWCharFromUtf8(SCHANNEL_SecPkgInfoA.Name, SCHANNEL_SecPkgInfoW_NameBuffer,
524 ARRAYSIZE(SCHANNEL_SecPkgInfoW_NameBuffer));
525 InitializeConstWCharFromUtf8(SCHANNEL_SecPkgInfoA.Comment, SCHANNEL_SecPkgInfoW_CommentBuffer,
526 ARRAYSIZE(SCHANNEL_SecPkgInfoW_CommentBuffer));
527 return TRUE;
528}