source: trunk/src/NTDLL/sec.cpp@ 2133

Last change on this file since 2133 was 2122, checked in by sandervl, 26 years ago

Partially implemented some Token & SID apis

File size: 19.3 KB
Line 
1/* $Id: sec.cpp,v 1.4 1999-12-18 20:01:14 sandervl Exp $ */
2
3/*
4 * Project Odin Software License can be found in LICENSE.TXT
5 * Win32 NT Runtime / NTDLL for OS/2
6 *
7 * Copyright 1998 original WINE Author
8 * Copyright 1998, 1999 Patrick Haller (phaller@gmx.net)
9 *
10 * Security functions
11 *
12 * Copyright 1996-1998 Marcus Meissner
13 */
14
15#include <stdlib.h>
16#include <string.h>
17#include <time.h>
18#include <ctype.h>
19#include <math.h>
20
21#include <os2win.h>
22#include "ntdll.h"
23
24/*
25#include "windef.h"
26#include "winbase.h"
27#include "winuser.h"
28#include "wine/winestring.h"
29#include "heap.h"
30#include "winnls.h"
31#include "winuser.h"
32#include "winerror.h"
33#include "stackframe.h"
34
35#include "winreg.h"
36*/
37
38/*
39 * SID FUNCTIONS
40 */
41
42/******************************************************************************
43 * RtlAllocateAndInitializeSid [NTDLL.265]
44 *
45 */
46BOOLEAN WINAPI RtlAllocateAndInitializeSid ( PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority,
47 BYTE nSubAuthorityCount,
48 DWORD nSubAuthority0,
49 DWORD nSubAuthority1,
50 DWORD nSubAuthority2,
51 DWORD nSubAuthority3,
52 DWORD nSubAuthority4,
53 DWORD nSubAuthority5,
54 DWORD nSubAuthority6,
55 DWORD nSubAuthority7,
56 PSID *pSid)
57{
58 dprintf(("NTDLL: RtlAllocateAndInitializeSid(%08xh,%08xh,%08xh,"
59 "%08xh,%08xh,%08xh,%08xh,%08xh,%08xh,%08xh,%08xh) not implemented.\n",
60 pIdentifierAuthority,
61 nSubAuthorityCount,
62 nSubAuthority0,
63 nSubAuthority1,
64 nSubAuthority2,
65 nSubAuthority3,
66 nSubAuthority4,
67 nSubAuthority5,
68 nSubAuthority6,
69 nSubAuthority7,
70 pSid));
71
72 *pSid = (PSID)Heap_Alloc(sizeof(SID)+nSubAuthorityCount*sizeof(DWORD));
73 if(*pSid == NULL) {
74 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
75 return FALSE;
76 }
77 (*pSid)->Revision = SID_REVISION;
78 (*pSid)->SubAuthorityCount = nSubAuthorityCount;
79 (*pSid)->SubAuthority[0] = nSubAuthority0;
80 (*pSid)->SubAuthority[1] = nSubAuthority1;
81 (*pSid)->SubAuthority[2] = nSubAuthority2;
82 (*pSid)->SubAuthority[3] = nSubAuthority3;
83 (*pSid)->SubAuthority[4] = nSubAuthority4;
84 (*pSid)->SubAuthority[5] = nSubAuthority5;
85 (*pSid)->SubAuthority[6] = nSubAuthority6;
86 (*pSid)->SubAuthority[7] = nSubAuthority7;
87 memcpy((PVOID)&(*pSid)->IdentifierAuthority, (PVOID)pIdentifierAuthority, sizeof(SID_IDENTIFIER_AUTHORITY));
88 return TRUE;
89}
90
91
92/******************************************************************************
93 * RtlEqualSid [NTDLL.352]
94 *
95 */
96BOOL WINAPI RtlEqualSid(PSID pSid1, PSID pSid2)
97{
98 dprintf(("NTDLL: RtlEqualSid(%08x, %08x) not implemented.\n",
99 pSid1,
100 pSid2));
101
102 return TRUE;
103}
104
105
106/******************************************************************************
107 * RtlFreeSid [NTDLL.376]
108 */
109VOID* WINAPI RtlFreeSid(PSID pSid)
110{
111 dprintf(("NTDLL: RtlFreeSid(%08xh) not implemented.\n",
112 pSid));
113
114 Heap_Free(pSid);
115 return (VOID*)TRUE; //??????
116}
117
118
119/**************************************************************************
120 * RtlLengthRequiredSid [NTDLL.427]
121 */
122DWORD WINAPI RtlLengthRequiredSid(DWORD nrofsubauths)
123{
124 dprintf (("NTDLL: RtlLengthRequiredSid(%08xh)\n",
125 nrofsubauths));
126
127 return sizeof(DWORD)*nrofsubauths+sizeof(SID);
128}
129
130
131/**************************************************************************
132 * RtlLengthSid [NTDLL.429]
133 */
134DWORD WINAPI RtlLengthSid(PSID sid)
135{
136 dprintf(("NTDLL: RtlLengthSid(%08xh)\n",
137 sid));
138
139 if (!sid)
140 return FALSE;
141
142 return sizeof(DWORD)*sid->SubAuthorityCount+sizeof(SID);
143}
144
145
146/**************************************************************************
147 * RtlInitializeSid [NTDLL.410]
148 */
149DWORD WINAPI RtlInitializeSid(PSID psid,
150 PSID_IDENTIFIER_AUTHORITY psidauth,
151 DWORD c)
152{
153 BYTE a = c & 0xff;
154
155 dprintf(("NTDLL: RtlInitializeSid(%08xh,%08xh,%08xh)\n",
156 psid,
157 psidauth,
158 c));
159
160 if (a>=SID_MAX_SUB_AUTHORITIES)
161 return a;
162
163 psid->SubAuthorityCount = a;
164 psid->Revision = SID_REVISION;
165 memcpy(&(psid->IdentifierAuthority),
166 psidauth,
167 sizeof(SID_IDENTIFIER_AUTHORITY));
168
169 return STATUS_SUCCESS;
170}
171
172
173/**************************************************************************
174 * RtlSubAuthoritySid [NTDLL.497]
175 */
176LPDWORD WINAPI RtlSubAuthoritySid(PSID psid,
177 DWORD nr)
178{
179 dprintf(("NTDLL: RtlSubAuthoritySid(%08xh,%08xh)\n",
180 psid,
181 nr));
182
183 return &(psid->SubAuthority[nr]);
184}
185
186
187/**************************************************************************
188 * RtlSubAuthorityCountSid [NTDLL.496]
189 */
190
191LPBYTE WINAPI RtlSubAuthorityCountSid(PSID psid)
192{
193 dprintf(("NTDLL: RtlSubAUthorityCountSid(%08xh)\n",
194 psid));
195
196 return ((LPBYTE)psid)+1;
197}
198
199
200/**************************************************************************
201 * RtlCopySid [NTDLL.302]
202 */
203DWORD WINAPI RtlCopySid(DWORD len,
204 PSID to,
205 PSID from)
206{
207 dprintf(("NTDLL: RtlCopySid(%08xh,%08xh,%08xh)\n",
208 len,
209 to,
210 from));
211
212 if (!from)
213 return 0;
214
215 if (len<(from->SubAuthorityCount*4+8))
216 return STATUS_BUFFER_TOO_SMALL;
217
218 memmove(to,
219 from,
220 from->SubAuthorityCount*4+8);
221
222 return STATUS_SUCCESS;
223}
224
225
226/*
227 * security descriptor functions
228 */
229
230/**************************************************************************
231 * RtlCreateSecurityDescriptor [NTDLL.313]
232 *
233 * RETURNS:
234 * 0 success,
235 * STATUS_INVALID_OWNER, STATUS_PRIVILEGE_NOT_HELD, STATUS_NO_INHERITANCE,
236 * STATUS_NO_MEMORY
237 */
238NTSTATUS WINAPI RtlCreateSecurityDescriptor(PSECURITY_DESCRIPTOR lpsd,
239 DWORD rev)
240{
241 dprintf(("NTDLL: RtlCreateSecurityDescriptor(%08xh,%08xh)\n",
242 lpsd,
243 rev));
244
245 if (rev!=SECURITY_DESCRIPTOR_REVISION)
246 return STATUS_UNKNOWN_REVISION;
247
248 memset(lpsd,
249 '\0',
250 sizeof(*lpsd));
251
252 lpsd->Revision = SECURITY_DESCRIPTOR_REVISION;
253
254 return STATUS_SUCCESS;
255}
256
257
258/**************************************************************************
259 * RtlValidSecurityDescriptor [NTDLL.313]
260 *
261 */
262NTSTATUS WINAPI RtlValidSecurityDescriptor(PSECURITY_DESCRIPTOR SecurityDescriptor)
263{
264 dprintf(("NTDLL: RtlValidSecurityDescriptor(%08xh)\n",
265 SecurityDescriptor));
266
267 if ( ! SecurityDescriptor )
268 return STATUS_INVALID_SECURITY_DESCR;
269
270 if ( SecurityDescriptor->Revision != SECURITY_DESCRIPTOR_REVISION )
271 return STATUS_UNKNOWN_REVISION;
272
273 return STATUS_SUCCESS;
274}
275
276
277/**************************************************************************
278 * RtlLengthSecurityDescriptor [NTDLL]
279 */
280ULONG WINAPI RtlLengthSecurityDescriptor(PSECURITY_DESCRIPTOR SecurityDescriptor)
281{
282 ULONG Size;
283
284 dprintf(("NTDLL: RtlLengthSecurityDescriptor(%08xh)\n",
285 SecurityDescriptor));
286
287 Size = SECURITY_DESCRIPTOR_MIN_LENGTH;
288 if ( SecurityDescriptor == NULL )
289 return 0;
290
291 if ( SecurityDescriptor->Owner != NULL )
292 Size += SecurityDescriptor->Owner->SubAuthorityCount;
293 if ( SecurityDescriptor->Group != NULL )
294 Size += SecurityDescriptor->Group->SubAuthorityCount;
295
296
297 if ( SecurityDescriptor->Sacl != NULL )
298 Size += SecurityDescriptor->Sacl->AclSize;
299 if ( SecurityDescriptor->Dacl != NULL )
300 Size += SecurityDescriptor->Dacl->AclSize;
301
302 return Size;
303}
304
305/******************************************************************************
306 * RtlGetDaclSecurityDescriptor [NTDLL]
307 *
308 */
309NTSTATUS WINAPI RtlGetDaclSecurityDescriptor(PSECURITY_DESCRIPTOR pSecurityDescriptor,
310 PBOOLEAN lpbDaclPresent,
311 PACL *pDacl,
312 PBOOLEAN lpbDaclDefaulted)
313{
314 dprintf(("NTDLL: RtlGetDaclSecurityDescriptor(%08xh,%08xh,%08xh,%08xh)\n",
315 pSecurityDescriptor,
316 lpbDaclPresent,
317 pDacl,
318 lpbDaclDefaulted));
319
320 if (pSecurityDescriptor->Revision != SECURITY_DESCRIPTOR_REVISION)
321 return STATUS_UNKNOWN_REVISION ;
322
323 *lpbDaclPresent = (SE_DACL_PRESENT & pSecurityDescriptor->Control);
324 if (*lpbDaclPresent ? 1 : 0)
325 {
326 if ( SE_SELF_RELATIVE & pSecurityDescriptor->Control)
327 {
328 *pDacl = (PACL) ((LPBYTE)pSecurityDescriptor + (DWORD)pSecurityDescriptor->Dacl);
329 }
330 else
331 {
332 *pDacl = pSecurityDescriptor->Dacl;
333 }
334 }
335
336 *lpbDaclDefaulted = (( SE_DACL_DEFAULTED & pSecurityDescriptor->Control ) ? 1 : 0);
337
338 return STATUS_SUCCESS;
339}
340
341
342/**************************************************************************
343 * RtlSetDaclSecurityDescriptor [NTDLL.483]
344 */
345NTSTATUS WINAPI RtlSetDaclSecurityDescriptor (PSECURITY_DESCRIPTOR lpsd,
346 BOOLEAN daclpresent,
347 PACL dacl,
348 BOOLEAN dacldefaulted)
349{
350 dprintf(("NTDLL: RtlSetDaclSecurityDescriptor(%08xh,%08xh,%08xh,%08xh)\n",
351 lpsd,
352 daclpresent,
353 dacl,
354 dacldefaulted));
355
356 if (lpsd->Revision!=SECURITY_DESCRIPTOR_REVISION)
357 return STATUS_UNKNOWN_REVISION;
358 if (lpsd->Control & SE_SELF_RELATIVE)
359 return STATUS_INVALID_SECURITY_DESCR;
360
361 if (!daclpresent)
362 {
363 lpsd->Control &= ~SE_DACL_PRESENT;
364 return TRUE;
365 }
366
367 lpsd->Control |= SE_DACL_PRESENT;
368 lpsd->Dacl = dacl;
369
370 if (dacldefaulted)
371 lpsd->Control |= SE_DACL_DEFAULTED;
372 else
373 lpsd->Control &= ~SE_DACL_DEFAULTED;
374
375 return STATUS_SUCCESS;
376}
377
378
379/******************************************************************************
380 * RtlGetSaclSecurityDescriptor [NTDLL]
381 *
382 */
383NTSTATUS WINAPI RtlGetSaclSecurityDescriptor(PSECURITY_DESCRIPTOR pSecurityDescriptor,
384 PBOOLEAN lpbSaclPresent,
385 PACL *pSacl,
386 PBOOLEAN lpbSaclDefaulted)
387{
388 dprintf(("NTDLL: RtlGetSaclSecurityDescriptor(%08xh,%08xh,%08xh,%08xh)\n",
389 pSecurityDescriptor,
390 lpbSaclPresent,
391 pSacl,
392 lpbSaclDefaulted));
393
394 if (pSecurityDescriptor->Revision != SECURITY_DESCRIPTOR_REVISION)
395 return STATUS_UNKNOWN_REVISION ;
396
397 *lpbSaclPresent = (SE_SACL_PRESENT & pSecurityDescriptor->Control);
398 if (*lpbSaclPresent ? 1 : 0)
399 {
400 if ( SE_SELF_RELATIVE & pSecurityDescriptor->Control)
401 {
402 *pSacl = (PACL) ((LPBYTE)pSecurityDescriptor + (DWORD)pSecurityDescriptor->Sacl);
403 }
404 else
405 {
406 *pSacl = pSecurityDescriptor->Sacl;
407 }
408 }
409
410 *lpbSaclDefaulted = (( SE_SACL_DEFAULTED & pSecurityDescriptor->Control ) ? 1 : 0);
411
412 return STATUS_SUCCESS;
413}
414
415
416/**************************************************************************
417 * RtlSetSaclSecurityDescriptor [NTDLL.488]
418 */
419NTSTATUS WINAPI RtlSetSaclSecurityDescriptor (PSECURITY_DESCRIPTOR lpsd,
420 BOOLEAN saclpresent,
421 PACL sacl,
422 BOOLEAN sacldefaulted)
423{
424 dprintf(("NTDLL: RtlSetSaclSecurityDescriptor(%08xh,%08xh,%08xh,%08xh)\n",
425 lpsd,
426 saclpresent,
427 sacl,
428 sacldefaulted));
429
430 if (lpsd->Revision!=SECURITY_DESCRIPTOR_REVISION)
431 return STATUS_UNKNOWN_REVISION;
432
433 if (lpsd->Control & SE_SELF_RELATIVE)
434 return STATUS_INVALID_SECURITY_DESCR;
435
436 if (!saclpresent)
437 {
438 lpsd->Control &= ~SE_SACL_PRESENT;
439 return 0;
440 }
441
442 lpsd->Control |= SE_SACL_PRESENT;
443 lpsd->Sacl = sacl;
444
445 if (sacldefaulted)
446 lpsd->Control |= SE_SACL_DEFAULTED;
447 else
448 lpsd->Control &= ~SE_SACL_DEFAULTED;
449
450 return STATUS_SUCCESS;
451}
452
453
454/**************************************************************************
455 * RtlGetOwnerSecurityDescriptor [NTDLL.488]
456 */
457NTSTATUS WINAPI RtlGetOwnerSecurityDescriptor(PSECURITY_DESCRIPTOR SecurityDescriptor,
458 PSID *Owner,
459 PBOOLEAN OwnerDefaulted)
460{
461 dprintf(("NTDLL: RtlGetOwnerSecurityDescriptor(%08xh,%08xh,%08xh)\n",
462 SecurityDescriptor,
463 Owner,
464 OwnerDefaulted));
465
466 if ( !SecurityDescriptor || !Owner || !OwnerDefaulted )
467 return STATUS_INVALID_PARAMETER;
468
469 *Owner = SecurityDescriptor->Owner;
470 if ( *Owner != NULL )
471 {
472 if ( SecurityDescriptor->Control & SE_OWNER_DEFAULTED )
473 *OwnerDefaulted = TRUE;
474 else
475 *OwnerDefaulted = FALSE;
476 }
477
478 return STATUS_SUCCESS;
479}
480
481
482/**************************************************************************
483 * RtlSetOwnerSecurityDescriptor [NTDLL.487]
484 */
485NTSTATUS WINAPI RtlSetOwnerSecurityDescriptor(PSECURITY_DESCRIPTOR lpsd,
486 PSID owner,
487 BOOLEAN ownerdefaulted)
488{
489 dprintf(("NTDLL: RtlSetOwnerSecurityDescriptor(%08x,%08xh,%08xh)\n",
490 lpsd,
491 owner,
492 ownerdefaulted));
493
494 if (lpsd->Revision!=SECURITY_DESCRIPTOR_REVISION)
495 return STATUS_UNKNOWN_REVISION;
496 if (lpsd->Control & SE_SELF_RELATIVE)
497 return STATUS_INVALID_SECURITY_DESCR;
498
499 lpsd->Owner = owner;
500 if (ownerdefaulted)
501 lpsd->Control |= SE_OWNER_DEFAULTED;
502 else
503 lpsd->Control &= ~SE_OWNER_DEFAULTED;
504
505 return STATUS_SUCCESS;
506}
507
508
509/**************************************************************************
510 * RtlSetGroupSecurityDescriptor [NTDLL.485]
511 */
512NTSTATUS WINAPI RtlSetGroupSecurityDescriptor (PSECURITY_DESCRIPTOR lpsd,
513 PSID group,
514 BOOLEAN groupdefaulted)
515{
516 dprintf(("NTDLL: RtlSetGroupSecurityDescriptor(%08xh,%08xh,%08xh)\n",
517 lpsd,
518 group,
519 groupdefaulted));
520
521 if (lpsd->Revision!=SECURITY_DESCRIPTOR_REVISION)
522 return STATUS_UNKNOWN_REVISION;
523 if (lpsd->Control & SE_SELF_RELATIVE)
524 return STATUS_INVALID_SECURITY_DESCR;
525
526 lpsd->Group = group;
527 if (groupdefaulted)
528 lpsd->Control |= SE_GROUP_DEFAULTED;
529 else
530 lpsd->Control &= ~SE_GROUP_DEFAULTED;
531
532 return STATUS_SUCCESS;
533}
534
535
536/**************************************************************************
537 * RtlGetGroupSecurityDescriptor [NTDLL]
538 */
539NTSTATUS WINAPI RtlGetGroupSecurityDescriptor(PSECURITY_DESCRIPTOR SecurityDescriptor,
540 PSID *Group,
541 PBOOLEAN GroupDefaulted)
542{
543 dprintf(("NTDLL: RtlGetGroupSecurityDescriptor(%08xh,%08xh,%08xh)\n",
544 SecurityDescriptor,
545 Group,
546 GroupDefaulted));
547
548 if ( !SecurityDescriptor || !Group || !GroupDefaulted )
549 return STATUS_INVALID_PARAMETER;
550
551 *Group = SecurityDescriptor->Group;
552 if ( *Group != NULL )
553 {
554 if ( SecurityDescriptor->Control & SE_GROUP_DEFAULTED )
555 *GroupDefaulted = TRUE;
556 else
557 *GroupDefaulted = FALSE;
558 }
559
560 return STATUS_SUCCESS;
561}
562
563
564/*
565 * access control list's
566 */
567
568/**************************************************************************
569 * RtlCreateAcl [NTDLL.306]
570 *
571 * NOTES
572 * This should return NTSTATUS
573 */
574DWORD WINAPI RtlCreateAcl(PACL acl,
575 DWORD size,
576 DWORD rev)
577{
578 dprintf(("NTDLL: RtlCreateAcl(%08xh,%08xh,%08xh)\n",
579 acl,
580 size,
581 rev));
582
583 if (rev!=ACL_REVISION)
584 return STATUS_INVALID_PARAMETER;
585 if (size<sizeof(ACL))
586 return STATUS_BUFFER_TOO_SMALL;
587 if (size>0xFFFF)
588 return STATUS_INVALID_PARAMETER;
589
590 memset(acl,'\0',sizeof(ACL));
591 acl->AclRevision = rev;
592 acl->AclSize = size;
593 acl->AceCount = 0;
594
595 return 0;
596}
597
598
599/**************************************************************************
600 * RtlFirstFreeAce [NTDLL.370]
601 * looks for the AceCount+1 ACE, and if it is still within the alloced
602 * ACL, return a pointer to it
603 */
604BOOLEAN WINAPI RtlFirstFreeAce(PACL acl,
605 PACE_HEADER *x)
606{
607 PACE_HEADER ace;
608 int i;
609
610 dprintf(("NTDLL: RtlFirstFreeAct(%08xh,%08xh)\n",
611 acl,
612 x));
613
614 *x = 0;
615 ace = (PACE_HEADER)(acl+1);
616 for (i=0;
617 i<acl->AceCount;
618 i++)
619 {
620 if ((DWORD)ace>=(((DWORD)acl)+acl->AclSize))
621 return 0;
622
623 ace = (PACE_HEADER)(((BYTE*)ace)+ace->AceSize);
624 }
625
626 if ((DWORD)ace>=(((DWORD)acl)+acl->AclSize))
627 return 0;
628
629 *x = ace;
630 return 1;
631}
632
633
634/**************************************************************************
635 * RtlAddAce [NTDLL.260]
636 */
637NTSTATUS WINAPI RtlAddAce(PACL acl,
638 DWORD rev,
639 DWORD xnrofaces,
640 PACE_HEADER acestart,
641 DWORD acelen)
642{
643 PACE_HEADER ace,targetace;
644 int nrofaces;
645
646 dprintf(("NTDLL: RtlAddAce(%08xh,%08xh,%08xh,%08xh,%08xh)\n",
647 acl,
648 rev,
649 xnrofaces,
650 acestart,
651 acelen));
652
653 if (acl->AclRevision != ACL_REVISION)
654 return STATUS_INVALID_PARAMETER;
655
656 if (!RtlFirstFreeAce(acl,&targetace))
657 return STATUS_INVALID_PARAMETER;
658
659 nrofaces=0;
660 ace=acestart;
661
662 while (((DWORD)ace-(DWORD)acestart)<acelen)
663 {
664 nrofaces++;
665 ace = (PACE_HEADER)(((BYTE*)ace)+ace->AceSize);
666 }
667
668 if ((DWORD)targetace+acelen>(DWORD)acl+acl->AclSize) /* too much aces */
669 return STATUS_INVALID_PARAMETER;
670
671 memcpy((LPBYTE)targetace,acestart,acelen);
672 acl->AceCount+=nrofaces;
673
674 return STATUS_SUCCESS;
675}
676
677
678/******************************************************************************
679 * RtlAddAccessAllowedAce [NTDLL]
680 */
681DWORD WINAPI RtlAddAccessAllowedAce(DWORD x1,
682 DWORD x2,
683 DWORD x3,
684 DWORD x4)
685{
686 dprintf(("NTDLL: RtlAddAccessAllowedAce(%08xh,%08xh,%08xh,%08xh) not implemented.\n",
687 x1,
688 x2,
689 x3,
690 x4));
691
692 return 0;
693}
694
695
696/******************************************************************************
697 * RtlGetAce [NTDLL]
698 */
699DWORD WINAPI RtlGetAce(PACL pAcl,
700 DWORD dwAceIndex,
701 LPVOID *pAce )
702{
703 dprintf(("NTDLL: RtlGetAce(%08x,%08x,%08x) not implemented.\n",
704 pAcl,
705 dwAceIndex,
706 pAce));
707
708 return 0;
709}
710
711
712/*
713 * misc
714 */
715
716/******************************************************************************
717 * RtlAdjustPrivilege [NTDLL]
718 */
719DWORD WINAPI RtlAdjustPrivilege(DWORD x1,
720 DWORD x2,
721 DWORD x3,
722 DWORD x4)
723{
724 dprintf(("NTDLL: RtlAdjustPrivilege(%08xh,%08xh,%08xh,%08xh) not implemented.\n",
725 x1,
726 x2,
727 x3,
728 x4));
729
730 return 0;
731}
732
Note: See TracBrowser for help on using the repository browser.