source: trunk/src/crypt32/ctl.c@ 21311

Last change on this file since 21311 was 21311, checked in by vladest, 16 years ago

Added CRYPT32 and MSCMS APIs support

File size: 18.4 KB
Line 
1/*
2 * Copyright 2008 Juan Lang
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 *
18 */
19
20#include <assert.h>
21#include <stdarg.h>
22
23#define NONAMELESSUNION
24#include "windef.h"
25#include "winbase.h"
26#include "wincrypt.h"
27#include "wine/debug.h"
28#include "crypt32_private.h"
29
30WINE_DEFAULT_DEBUG_CHANNEL(crypt);
31
32#define CtlContext_CopyProperties(to, from) \
33 Context_CopyProperties((to), (from), sizeof(CTL_CONTEXT))
34
35BOOL WINAPI CertAddCTLContextToStore(HCERTSTORE hCertStore,
36 PCCTL_CONTEXT pCtlContext, DWORD dwAddDisposition,
37 PCCTL_CONTEXT* ppStoreContext)
38{
39 PWINECRYPT_CERTSTORE store = (PWINECRYPT_CERTSTORE)hCertStore;
40 BOOL ret = TRUE;
41 PCCTL_CONTEXT toAdd = NULL, existing = NULL;
42
43 TRACE("(%p, %p, %08x, %p)\n", hCertStore, pCtlContext, dwAddDisposition,
44 ppStoreContext);
45
46 if (dwAddDisposition != CERT_STORE_ADD_ALWAYS)
47 {
48 existing = CertFindCTLInStore(hCertStore, 0, 0, CTL_FIND_EXISTING,
49 pCtlContext, NULL);
50 }
51
52 switch (dwAddDisposition)
53 {
54 case CERT_STORE_ADD_ALWAYS:
55 toAdd = CertDuplicateCTLContext(pCtlContext);
56 break;
57 case CERT_STORE_ADD_NEW:
58 if (existing)
59 {
60 TRACE("found matching CTL, not adding\n");
61 SetLastError(CRYPT_E_EXISTS);
62 ret = FALSE;
63 }
64 else
65 toAdd = CertDuplicateCTLContext(pCtlContext);
66 break;
67 case CERT_STORE_ADD_NEWER:
68 if (existing)
69 {
70 LONG newer = CompareFileTime(&existing->pCtlInfo->ThisUpdate,
71 &pCtlContext->pCtlInfo->ThisUpdate);
72
73 if (newer < 0)
74 toAdd = CertDuplicateCTLContext(pCtlContext);
75 else
76 {
77 TRACE("existing CTL is newer, not adding\n");
78 SetLastError(CRYPT_E_EXISTS);
79 ret = FALSE;
80 }
81 }
82 else
83 toAdd = CertDuplicateCTLContext(pCtlContext);
84 break;
85 case CERT_STORE_ADD_REPLACE_EXISTING:
86 toAdd = CertDuplicateCTLContext(pCtlContext);
87 break;
88 case CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES:
89 toAdd = CertDuplicateCTLContext(pCtlContext);
90 if (existing)
91 CtlContext_CopyProperties(toAdd, existing);
92 break;
93 case CERT_STORE_ADD_USE_EXISTING:
94 if (existing)
95 CtlContext_CopyProperties(existing, pCtlContext);
96 break;
97 default:
98 FIXME("Unimplemented add disposition %d\n", dwAddDisposition);
99 ret = FALSE;
100 }
101
102 if (toAdd)
103 {
104 if (store)
105 ret = store->ctls.addContext(store, (void *)toAdd,
106 (void *)existing, (const void **)ppStoreContext);
107 else if (ppStoreContext)
108 *ppStoreContext = CertDuplicateCTLContext(toAdd);
109 CertFreeCTLContext(toAdd);
110 }
111 CertFreeCTLContext(existing);
112
113 TRACE("returning %d\n", ret);
114 return ret;
115}
116
117BOOL WINAPI CertAddEncodedCTLToStore(HCERTSTORE hCertStore,
118 DWORD dwMsgAndCertEncodingType, const BYTE *pbCtlEncoded, DWORD cbCtlEncoded,
119 DWORD dwAddDisposition, PCCTL_CONTEXT *ppCtlContext)
120{
121 PCCTL_CONTEXT ctl = CertCreateCTLContext(dwMsgAndCertEncodingType,
122 pbCtlEncoded, cbCtlEncoded);
123 BOOL ret;
124
125 TRACE("(%p, %08x, %p, %d, %08x, %p)\n", hCertStore,
126 dwMsgAndCertEncodingType, pbCtlEncoded, cbCtlEncoded, dwAddDisposition,
127 ppCtlContext);
128
129 if (ctl)
130 {
131 ret = CertAddCTLContextToStore(hCertStore, ctl, dwAddDisposition,
132 ppCtlContext);
133 CertFreeCTLContext(ctl);
134 }
135 else
136 ret = FALSE;
137 return ret;
138}
139
140PCCTL_CONTEXT WINAPI CertEnumCTLsInStore(HCERTSTORE hCertStore,
141 PCCTL_CONTEXT pPrev)
142{
143 WINECRYPT_CERTSTORE *hcs = (WINECRYPT_CERTSTORE *)hCertStore;
144 PCCTL_CONTEXT ret;
145
146 TRACE("(%p, %p)\n", hCertStore, pPrev);
147 if (!hCertStore)
148 ret = NULL;
149 else if (hcs->dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
150 ret = NULL;
151 else
152 ret = (PCCTL_CONTEXT)hcs->ctls.enumContext(hcs, (void *)pPrev);
153 return ret;
154}
155
156typedef BOOL (*CtlCompareFunc)(PCCTL_CONTEXT pCtlContext, DWORD dwType,
157 DWORD dwFlags, const void *pvPara);
158
159static BOOL compare_ctl_any(PCCTL_CONTEXT pCtlContext, DWORD dwType,
160 DWORD dwFlags, const void *pvPara)
161{
162 return TRUE;
163}
164
165static BOOL compare_ctl_by_md5_hash(PCCTL_CONTEXT pCtlContext, DWORD dwType,
166 DWORD dwFlags, const void *pvPara)
167{
168 BOOL ret;
169 BYTE hash[16];
170 DWORD size = sizeof(hash);
171
172 ret = CertGetCTLContextProperty(pCtlContext, CERT_MD5_HASH_PROP_ID, hash,
173 &size);
174 if (ret)
175 {
176 const CRYPT_HASH_BLOB *pHash = (const CRYPT_HASH_BLOB *)pvPara;
177
178 if (size == pHash->cbData)
179 ret = !memcmp(pHash->pbData, hash, size);
180 else
181 ret = FALSE;
182 }
183 return ret;
184}
185
186static BOOL compare_ctl_by_sha1_hash(PCCTL_CONTEXT pCtlContext, DWORD dwType,
187 DWORD dwFlags, const void *pvPara)
188{
189 BOOL ret;
190 BYTE hash[20];
191 DWORD size = sizeof(hash);
192
193 ret = CertGetCTLContextProperty(pCtlContext, CERT_SHA1_HASH_PROP_ID, hash,
194 &size);
195 if (ret)
196 {
197 const CRYPT_HASH_BLOB *pHash = (const CRYPT_HASH_BLOB *)pvPara;
198
199 if (size == pHash->cbData)
200 ret = !memcmp(pHash->pbData, hash, size);
201 else
202 ret = FALSE;
203 }
204 return ret;
205}
206
207static BOOL compare_ctl_existing(PCCTL_CONTEXT pCtlContext, DWORD dwType,
208 DWORD dwFlags, const void *pvPara)
209{
210 BOOL ret;
211
212 if (pvPara)
213 {
214 PCCTL_CONTEXT ctl = (PCCTL_CONTEXT)pvPara;
215
216 if (pCtlContext->cbCtlContext == ctl->cbCtlContext)
217 {
218 if (ctl->cbCtlContext)
219 ret = !memcmp(pCtlContext->pbCtlContext, ctl->pbCtlContext,
220 ctl->cbCtlContext);
221 else
222 ret = TRUE;
223 }
224 else
225 ret = FALSE;
226 }
227 else
228 ret = FALSE;
229 return ret;
230}
231
232PCCTL_CONTEXT WINAPI CertFindCTLInStore(HCERTSTORE hCertStore,
233 DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType,
234 const void *pvFindPara, PCCTL_CONTEXT pPrevCtlContext)
235{
236 PCCTL_CONTEXT ret;
237 CtlCompareFunc compare;
238
239 TRACE("(%p, %d, %d, %d, %p, %p)\n", hCertStore, dwCertEncodingType,
240 dwFindFlags, dwFindType, pvFindPara, pPrevCtlContext);
241
242 switch (dwFindType)
243 {
244 case CTL_FIND_ANY:
245 compare = compare_ctl_any;
246 break;
247 case CTL_FIND_SHA1_HASH:
248 compare = compare_ctl_by_sha1_hash;
249 break;
250 case CTL_FIND_MD5_HASH:
251 compare = compare_ctl_by_md5_hash;
252 break;
253 case CTL_FIND_EXISTING:
254 compare = compare_ctl_existing;
255 break;
256 default:
257 FIXME("find type %08x unimplemented\n", dwFindType);
258 compare = NULL;
259 }
260
261 if (compare)
262 {
263 BOOL matches = FALSE;
264
265 ret = pPrevCtlContext;
266 do {
267 ret = CertEnumCTLsInStore(hCertStore, ret);
268 if (ret)
269 matches = compare(ret, dwFindType, dwFindFlags, pvFindPara);
270 } while (ret != NULL && !matches);
271 if (!ret)
272 SetLastError(CRYPT_E_NOT_FOUND);
273 }
274 else
275 {
276 SetLastError(CRYPT_E_NOT_FOUND);
277 ret = NULL;
278 }
279 return ret;
280}
281
282BOOL WINAPI CertDeleteCTLFromStore(PCCTL_CONTEXT pCtlContext)
283{
284 BOOL ret;
285
286 TRACE("(%p)\n", pCtlContext);
287
288 if (!pCtlContext)
289 ret = TRUE;
290 else if (!pCtlContext->hCertStore)
291 {
292 ret = TRUE;
293 CertFreeCTLContext(pCtlContext);
294 }
295 else
296 {
297 PWINECRYPT_CERTSTORE hcs =
298 (PWINECRYPT_CERTSTORE)pCtlContext->hCertStore;
299
300 if (hcs->dwMagic != WINE_CRYPTCERTSTORE_MAGIC)
301 ret = FALSE;
302 else
303 ret = hcs->ctls.deleteContext(hcs, (void *)pCtlContext);
304 CertFreeCTLContext(pCtlContext);
305 }
306 return ret;
307}
308
309PCCTL_CONTEXT WINAPI CertCreateCTLContext(DWORD dwMsgAndCertEncodingType,
310 const BYTE *pbCtlEncoded, DWORD cbCtlEncoded)
311{
312 PCTL_CONTEXT ctl = NULL;
313 HCRYPTMSG msg;
314 BOOL ret;
315 BYTE *content = NULL;
316 DWORD contentSize = 0, size;
317 PCTL_INFO ctlInfo = NULL;
318
319 TRACE("(%08x, %p, %d)\n", dwMsgAndCertEncodingType, pbCtlEncoded,
320 cbCtlEncoded);
321
322 if (GET_CERT_ENCODING_TYPE(dwMsgAndCertEncodingType) != X509_ASN_ENCODING)
323 {
324 SetLastError(E_INVALIDARG);
325 return NULL;
326 }
327 if (!pbCtlEncoded || !cbCtlEncoded)
328 {
329 SetLastError(ERROR_INVALID_DATA);
330 return NULL;
331 }
332 msg = CryptMsgOpenToDecode(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, 0, 0,
333 0, NULL, NULL);
334 if (!msg)
335 return NULL;
336 ret = CryptMsgUpdate(msg, pbCtlEncoded, cbCtlEncoded, TRUE);
337 if (!ret)
338 {
339 SetLastError(ERROR_INVALID_DATA);
340 goto end;
341 }
342 /* Check that it's really a CTL */
343 ret = CryptMsgGetParam(msg, CMSG_INNER_CONTENT_TYPE_PARAM, 0, NULL, &size);
344 if (ret)
345 {
346 char *innerContent = CryptMemAlloc(size);
347
348 if (innerContent)
349 {
350 ret = CryptMsgGetParam(msg, CMSG_INNER_CONTENT_TYPE_PARAM, 0,
351 innerContent, &size);
352 if (ret)
353 {
354 if (strcmp(innerContent, szOID_CTL))
355 {
356 SetLastError(ERROR_INVALID_DATA);
357 ret = FALSE;
358 }
359 }
360 CryptMemFree(innerContent);
361 }
362 else
363 {
364 SetLastError(ERROR_OUTOFMEMORY);
365 ret = FALSE;
366 }
367 }
368 if (!ret)
369 goto end;
370 ret = CryptMsgGetParam(msg, CMSG_CONTENT_PARAM, 0, NULL, &contentSize);
371 if (!ret)
372 goto end;
373 content = CryptMemAlloc(contentSize);
374 if (content)
375 {
376 ret = CryptMsgGetParam(msg, CMSG_CONTENT_PARAM, 0, content,
377 &contentSize);
378 if (ret)
379 {
380 ret = CryptDecodeObjectEx(dwMsgAndCertEncodingType, PKCS_CTL,
381 content, contentSize, CRYPT_DECODE_ALLOC_FLAG, NULL,
382 (BYTE *)&ctlInfo, &size);
383 if (ret)
384 {
385 ctl = Context_CreateDataContext(sizeof(CTL_CONTEXT));
386 if (ctl)
387 {
388 BYTE *data = CryptMemAlloc(cbCtlEncoded);
389
390 if (data)
391 {
392 memcpy(data, pbCtlEncoded, cbCtlEncoded);
393 ctl->dwMsgAndCertEncodingType =
394 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
395 ctl->pbCtlEncoded = data;
396 ctl->cbCtlEncoded = cbCtlEncoded;
397 ctl->pCtlInfo = ctlInfo;
398 ctl->hCertStore = NULL;
399 ctl->hCryptMsg = msg;
400 ctl->pbCtlContext = content;
401 ctl->cbCtlContext = contentSize;
402 }
403 else
404 {
405 SetLastError(ERROR_OUTOFMEMORY);
406 ret = FALSE;
407 }
408 }
409 else
410 {
411 SetLastError(ERROR_OUTOFMEMORY);
412 ret = FALSE;
413 }
414 }
415 }
416 }
417 else
418 {
419 SetLastError(ERROR_OUTOFMEMORY);
420 ret = FALSE;
421 }
422
423end:
424 if (!ret)
425 {
426 CryptMemFree(ctl);
427 ctl = NULL;
428 LocalFree(ctlInfo);
429 CryptMemFree(content);
430 CryptMsgClose(msg);
431 }
432 return (PCCTL_CONTEXT)ctl;
433}
434
435PCCTL_CONTEXT WINAPI CertDuplicateCTLContext(PCCTL_CONTEXT pCtlContext)
436{
437 TRACE("(%p)\n", pCtlContext);
438 Context_AddRef((void *)pCtlContext, sizeof(CTL_CONTEXT));
439 return pCtlContext;
440}
441
442static void CTLDataContext_Free(void *context)
443{
444 PCTL_CONTEXT ctlContext = (PCTL_CONTEXT)context;
445
446 CryptMsgClose(ctlContext->hCryptMsg);
447 CryptMemFree(ctlContext->pbCtlEncoded);
448 CryptMemFree(ctlContext->pbCtlContext);
449 LocalFree(ctlContext->pCtlInfo);
450}
451
452BOOL WINAPI CertFreeCTLContext(PCCTL_CONTEXT pCTLContext)
453{
454 TRACE("(%p)\n", pCTLContext);
455
456 if (pCTLContext)
457 Context_Release((void *)pCTLContext, sizeof(CTL_CONTEXT),
458 CTLDataContext_Free);
459 return TRUE;
460}
461
462DWORD WINAPI CertEnumCTLContextProperties(PCCTL_CONTEXT pCTLContext,
463 DWORD dwPropId)
464{
465 PCONTEXT_PROPERTY_LIST properties = Context_GetProperties(
466 (void *)pCTLContext, sizeof(CTL_CONTEXT));
467 DWORD ret;
468
469 TRACE("(%p, %d)\n", pCTLContext, dwPropId);
470
471 if (properties)
472 ret = ContextPropertyList_EnumPropIDs(properties, dwPropId);
473 else
474 ret = 0;
475 return ret;
476}
477
478static BOOL CTLContext_SetProperty(PCCTL_CONTEXT context, DWORD dwPropId,
479 DWORD dwFlags, const void *pvData);
480
481static BOOL CTLContext_GetHashProp(PCCTL_CONTEXT context, DWORD dwPropId,
482 ALG_ID algID, const BYTE *toHash, DWORD toHashLen, void *pvData,
483 DWORD *pcbData)
484{
485 BOOL ret = CryptHashCertificate(0, algID, 0, toHash, toHashLen, pvData,
486 pcbData);
487 if (ret)
488 {
489 CRYPT_DATA_BLOB blob = { *pcbData, pvData };
490
491 ret = CTLContext_SetProperty(context, dwPropId, 0, &blob);
492 }
493 return ret;
494}
495
496static BOOL CTLContext_GetProperty(PCCTL_CONTEXT context, DWORD dwPropId,
497 void *pvData, DWORD *pcbData)
498{
499 PCONTEXT_PROPERTY_LIST properties =
500 Context_GetProperties(context, sizeof(CTL_CONTEXT));
501 BOOL ret;
502 CRYPT_DATA_BLOB blob;
503
504 TRACE("(%p, %d, %p, %p)\n", context, dwPropId, pvData, pcbData);
505
506 if (properties)
507 ret = ContextPropertyList_FindProperty(properties, dwPropId, &blob);
508 else
509 ret = FALSE;
510 if (ret)
511 {
512 if (!pvData)
513 *pcbData = blob.cbData;
514 else if (*pcbData < blob.cbData)
515 {
516 SetLastError(ERROR_MORE_DATA);
517 *pcbData = blob.cbData;
518 ret = FALSE;
519 }
520 else
521 {
522 memcpy(pvData, blob.pbData, blob.cbData);
523 *pcbData = blob.cbData;
524 }
525 }
526 else
527 {
528 /* Implicit properties */
529 switch (dwPropId)
530 {
531 case CERT_SHA1_HASH_PROP_ID:
532 ret = CTLContext_GetHashProp(context, dwPropId, CALG_SHA1,
533 context->pbCtlEncoded, context->cbCtlEncoded, pvData, pcbData);
534 break;
535 case CERT_MD5_HASH_PROP_ID:
536 ret = CTLContext_GetHashProp(context, dwPropId, CALG_MD5,
537 context->pbCtlEncoded, context->cbCtlEncoded, pvData, pcbData);
538 break;
539 default:
540 SetLastError(CRYPT_E_NOT_FOUND);
541 }
542 }
543 TRACE("returning %d\n", ret);
544 return ret;
545}
546
547BOOL WINAPI CertGetCTLContextProperty(PCCTL_CONTEXT pCTLContext,
548 DWORD dwPropId, void *pvData, DWORD *pcbData)
549{
550 BOOL ret;
551
552 TRACE("(%p, %d, %p, %p)\n", pCTLContext, dwPropId, pvData, pcbData);
553
554 switch (dwPropId)
555 {
556 case 0:
557 case CERT_CERT_PROP_ID:
558 case CERT_CRL_PROP_ID:
559 case CERT_CTL_PROP_ID:
560 SetLastError(E_INVALIDARG);
561 ret = FALSE;
562 break;
563 case CERT_ACCESS_STATE_PROP_ID:
564 if (!pvData)
565 {
566 *pcbData = sizeof(DWORD);
567 ret = TRUE;
568 }
569 else if (*pcbData < sizeof(DWORD))
570 {
571 SetLastError(ERROR_MORE_DATA);
572 *pcbData = sizeof(DWORD);
573 ret = FALSE;
574 }
575 else
576 {
577 if (pCTLContext->hCertStore)
578 ret = CertGetStoreProperty(pCTLContext->hCertStore, dwPropId,
579 pvData, pcbData);
580 else
581 *(DWORD *)pvData = 0;
582 ret = TRUE;
583 }
584 break;
585 default:
586 ret = CTLContext_GetProperty(pCTLContext, dwPropId, pvData,
587 pcbData);
588 }
589 return ret;
590}
591
592static BOOL CTLContext_SetProperty(PCCTL_CONTEXT context, DWORD dwPropId,
593 DWORD dwFlags, const void *pvData)
594{
595 PCONTEXT_PROPERTY_LIST properties =
596 Context_GetProperties(context, sizeof(CTL_CONTEXT));
597 BOOL ret;
598
599 TRACE("(%p, %d, %08x, %p)\n", context, dwPropId, dwFlags, pvData);
600
601 if (!properties)
602 ret = FALSE;
603 else if (!pvData)
604 {
605 ContextPropertyList_RemoveProperty(properties, dwPropId);
606 ret = TRUE;
607 }
608 else
609 {
610 switch (dwPropId)
611 {
612 case CERT_AUTO_ENROLL_PROP_ID:
613 case CERT_CTL_USAGE_PROP_ID: /* same as CERT_ENHKEY_USAGE_PROP_ID */
614 case CERT_DESCRIPTION_PROP_ID:
615 case CERT_FRIENDLY_NAME_PROP_ID:
616 case CERT_HASH_PROP_ID:
617 case CERT_KEY_IDENTIFIER_PROP_ID:
618 case CERT_MD5_HASH_PROP_ID:
619 case CERT_NEXT_UPDATE_LOCATION_PROP_ID:
620 case CERT_PUBKEY_ALG_PARA_PROP_ID:
621 case CERT_PVK_FILE_PROP_ID:
622 case CERT_SIGNATURE_HASH_PROP_ID:
623 case CERT_ISSUER_PUBLIC_KEY_MD5_HASH_PROP_ID:
624 case CERT_SUBJECT_NAME_MD5_HASH_PROP_ID:
625 case CERT_SUBJECT_PUBLIC_KEY_MD5_HASH_PROP_ID:
626 case CERT_ENROLLMENT_PROP_ID:
627 case CERT_CROSS_CERT_DIST_POINTS_PROP_ID:
628 case CERT_RENEWAL_PROP_ID:
629 {
630 PCRYPT_DATA_BLOB blob = (PCRYPT_DATA_BLOB)pvData;
631
632 ret = ContextPropertyList_SetProperty(properties, dwPropId,
633 blob->pbData, blob->cbData);
634 break;
635 }
636 case CERT_DATE_STAMP_PROP_ID:
637 ret = ContextPropertyList_SetProperty(properties, dwPropId,
638 (const BYTE *)pvData, sizeof(FILETIME));
639 break;
640 default:
641 FIXME("%d: stub\n", dwPropId);
642 ret = FALSE;
643 }
644 }
645 TRACE("returning %d\n", ret);
646 return ret;
647}
648
649BOOL WINAPI CertSetCTLContextProperty(PCCTL_CONTEXT pCTLContext,
650 DWORD dwPropId, DWORD dwFlags, const void *pvData)
651{
652 BOOL ret;
653
654 TRACE("(%p, %d, %08x, %p)\n", pCTLContext, dwPropId, dwFlags, pvData);
655
656 /* Handle special cases for "read-only"/invalid prop IDs. Windows just
657 * crashes on most of these, I'll be safer.
658 */
659 switch (dwPropId)
660 {
661 case 0:
662 case CERT_ACCESS_STATE_PROP_ID:
663 case CERT_CERT_PROP_ID:
664 case CERT_CRL_PROP_ID:
665 case CERT_CTL_PROP_ID:
666 SetLastError(E_INVALIDARG);
667 return FALSE;
668 }
669 ret = CTLContext_SetProperty(pCTLContext, dwPropId, dwFlags, pvData);
670 TRACE("returning %d\n", ret);
671 return ret;
672}
Note: See TracBrowser for help on using the repository browser.