2 Samba CIFS implementation
3 Registry backend for REGF files
4 Copyright (C) 2005 Jelmer Vernooij, jelmer@samba.org
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
22 #include "system/filesys.h"
23 #include "system/time.h"
24 #include "lib/registry/tdr_regf.h"
25 #include "librpc/gen_ndr/ndr_security.h"
28 * - Return error codes that make more sense
33 * Read HBIN blocks into memory
38 struct hbin_block
**hbins
;
39 struct regf_hdr
*header
;
42 static struct hbin_block
*hbin_by_offset (const struct regf_data
*data
, uint32_t offset
, uint32_t *rel_offset
)
46 for (i
= 0; data
->hbins
[i
]; i
++) {
47 if (offset
>= data
->hbins
[i
]->offset_from_first
&&
48 offset
< data
->hbins
[i
]->offset_from_first
+
49 data
->hbins
[i
]->offset_to_next
) {
51 *rel_offset
= offset
- data
->hbins
[i
]->offset_from_first
- 0x20;
52 return data
->hbins
[i
];
60 * Validate a regf header
61 * For now, do nothing, but we should check the checksum
63 static uint32_t regf_hdr_checksum(const uint8_t *buffer
)
65 uint32_t checksum
= 0, x
;
68 for (i
= 0; i
< 0x01FB; i
+= 4) {
76 static DATA_BLOB
hbin_get(const struct regf_data
*data
, uint32_t offset
)
79 struct hbin_block
*hbin
;
84 hbin
= hbin_by_offset(data
, offset
, &rel_offset
);
87 DEBUG(1, ("Can't find HBIN containing 0x%04x\n", offset
));
91 ret
.length
= IVAL(hbin
->data
, rel_offset
);
92 if (!(ret
.length
& 0x80000000)) {
93 DEBUG(0, ("Trying to use dirty block at 0x%04x\n", offset
));
98 ret
.length
= (ret
.length
^ 0xffffffff) + 1;
100 ret
.length
-= 4; /* 4 bytes for the length... */
101 ret
.data
= hbin
->data
+
102 (offset
- hbin
->offset_from_first
- 0x20) + 4;
107 static BOOL
hbin_get_tdr (struct regf_data
*regf
, uint32_t offset
, tdr_pull_fn_t pull_fn
, void *p
)
110 struct tdr_pull
*pull
;
112 data
= hbin_get(regf
, offset
);
114 DEBUG(1, ("Unable to get data at 0x%04x\n", offset
));
118 pull
= talloc_zero(regf
, struct tdr_pull
);
121 if (NT_STATUS_IS_ERR(pull_fn(pull
, p
))) {
122 DEBUG(1, ("Error parsing record at 0x%04x using tdr\n", offset
));
127 /* FIXME: Free pull ! */
132 /* Allocate some new data */
133 static DATA_BLOB
hbin_alloc (struct regf_data
*data
, uint32_t size
, uint32_t *offset
)
136 uint32_t rel_offset
= -1; /* Relative offset ! */
137 struct hbin_block
*hbin
= NULL
;
143 size
+= 4; /* Need to include uint32 for the length */
145 /* Allocate as a multiple of 8 */
146 size
= (size
+ 7) & ~7;
151 for (i
= 0; (hbin
= data
->hbins
[i
]); i
++) {
154 for (j
= 0; j
< hbin
->offset_to_next
-0x20; j
+= my_size
) {
155 my_size
= IVAL(hbin
->data
, j
);
156 uint32_t header
= IVAL(hbin
->data
, j
+ 4);
158 if (my_size
== 0x0) {
159 DEBUG(0, ("Invalid zero-length block! File is corrupt.\n"));
163 if (my_size
% 8 != 0) {
164 DEBUG(0, ("Encountered non-aligned block!\n"));
167 if (my_size
& 0x80000000) { /* Used... */
168 my_size
= (my_size
^ 0xffffffff) + 1;
169 } else if (my_size
== size
) { /* exact match */
171 DEBUG(4, ("Found free block of exact size %d in middle of HBIN\n", size
));
173 } else if (my_size
> size
) { /* data will remain */
175 SIVAL(hbin
->data
, rel_offset
+size
, my_size
-size
);
176 DEBUG(4, ("Found free block of size %d (needing %d) in middle of HBIN\n", my_size
, size
));
180 if (header
== 0xffffffff &&
181 hbin
->offset_to_next
-rel_offset
>= size
) {
184 DEBUG(4, ("Found free block of size %d at end of HBIN\n", size
));
185 /* Mark new free block size */
186 SIVAL(hbin
->data
, rel_offset
+size
,hbin
->offset_to_next
- rel_offset
- size
- 0x20);
187 SIVAL(hbin
->data
, rel_offset
+size
+0x4, 0xffffffff);
191 if (header
== 0xffffffff) {
196 if (rel_offset
!= -1)
200 /* No space available in previous hbins,
201 * allocate new one */
202 if (data
->hbins
[i
] == NULL
) {
203 DEBUG(4, ("No space available in other HBINs for block of size %d, allocating new HBIN\n", size
));
204 data
->hbins
= talloc_realloc(data
, data
->hbins
, struct hbin_block
*, i
+2);
205 hbin
= talloc(data
->hbins
, struct hbin_block
);
206 data
->hbins
[i
] = hbin
;
207 data
->hbins
[i
+1] = NULL
;
209 hbin
->HBIN_ID
= talloc_strdup(hbin
, "hbin");
210 hbin
->offset_from_first
= (i
== 0?0:data
->hbins
[i
-1]->offset_from_first
+data
->hbins
[i
-1]->offset_to_next
);
211 hbin
->offset_to_next
= 0x1000;
212 hbin
->unknown
[0] = 0;
213 hbin
->unknown
[0] = 0;
214 unix_to_nt_time(&hbin
->last_change
, time(NULL
));
215 hbin
->block_size
= hbin
->offset_to_next
;
216 hbin
->data
= talloc_zero_array(hbin
, uint8_t, hbin
->block_size
- 0x20);
219 SIVAL(hbin
->data
, size
, hbin
->block_size
- size
- 0x20);
220 SIVAL(hbin
->data
, size
+ 0x4, 0xffffffff);
223 /* Set size and mark as used */
224 SIVAL(hbin
->data
, rel_offset
, size
| 0x80000000);
226 ret
.data
= hbin
->data
+ rel_offset
+ 0x4; /* Skip past length */
227 ret
.length
= size
- 0x4;
229 uint32_t new_rel_offset
;
230 *offset
= hbin
->offset_from_first
+ rel_offset
+ 0x20;
231 SMB_ASSERT(hbin_by_offset(data
, *offset
, &new_rel_offset
) == hbin
);
232 SMB_ASSERT(new_rel_offset
== rel_offset
);
238 /* Store a data blob. Return the offset at which it was stored */
239 static uint32_t hbin_store (struct regf_data
*data
, DATA_BLOB blob
)
242 DATA_BLOB dest
= hbin_alloc(data
, blob
.length
, &ret
);
244 memcpy(dest
.data
, blob
.data
, blob
.length
);
249 static uint32_t hbin_store_tdr (struct regf_data
*data
, tdr_push_fn_t push_fn
, void *p
)
251 struct tdr_push
*push
= talloc_zero(data
, struct tdr_push
);
254 if (NT_STATUS_IS_ERR(push_fn(push
, p
))) {
255 DEBUG(0, ("Error during push\n"));
259 ret
= hbin_store(data
, push
->data
);
267 /* Free existing data */
268 static void hbin_free (struct regf_data
*data
, uint32_t offset
)
272 struct hbin_block
*hbin
;
274 SMB_ASSERT (offset
> 0);
276 hbin
= hbin_by_offset(data
, offset
, &rel_offset
);
281 /* Get original size */
282 size
= IVAL(hbin
->data
, rel_offset
);
284 if (!(size
& 0x80000000)) {
285 DEBUG(1, ("Trying to free already freed block at 0x%04x\n", offset
));
289 /* Mark block as free */
290 SIVAL(hbin
->data
, rel_offset
, size
&~ 0x80000000);
293 /* Store a data blob data was already stored, but hsa changed in size
294 * Will try to save it at the current location if possible, otherwise
295 * does a free + store */
296 static uint32_t hbin_store_resize (struct regf_data
*data
, uint32_t orig_offset
, DATA_BLOB blob
)
299 struct hbin_block
*hbin
= hbin_by_offset(data
, orig_offset
, &rel_offset
);
302 uint32_t needed_size
;
303 uint32_t possible_size
;
306 SMB_ASSERT(orig_offset
> 0);
309 return hbin_store(data
, blob
);
311 /* Get original size */
312 orig_size
= IVAL(hbin
->data
, rel_offset
);
314 needed_size
= blob
.length
+ 4; /* Add uint32 containing length */
315 needed_size
= (needed_size
+ 7) & ~7; /* Align */
317 /* Fits into current allocated block */
318 if (orig_size
>= needed_size
) {
319 memcpy(hbin
->data
+ rel_offset
+ 0x4, blob
.data
, blob
.length
);
323 possible_size
= orig_size
;
325 /* Check if it can be combined with the next few free records */
327 i
< hbin
->offset_to_next
- 0x20;
330 if (IVAL(hbin
->data
, i
) & 0x80000000) /* Used */
333 my_size
= IVAL(hbin
->data
, i
);
334 header
= IVAL(hbin
->data
, i
+ 4);
335 if (header
== 0xffffffff) {
336 possible_size
= hbin
->offset_to_next
- 0x20 - rel_offset
;
337 } else if (my_size
== 0x0) {
338 DEBUG(0, ("Invalid zero-length block! File is corrupt.\n"));
341 possible_size
+= my_size
;
344 if (possible_size
>= blob
.length
) {
345 SIVAL(hbin
->data
, rel_offset
, possible_size
);
346 memcpy(hbin
->data
+ rel_offset
+ 0x4, blob
.data
, blob
.length
);
350 if (header
== 0xffffffff)
354 hbin_free(data
, orig_offset
);
355 return hbin_store(data
, blob
);
358 static uint32_t hbin_store_tdr_resize (struct regf_data
*regf
, tdr_push_fn_t push_fn
, uint32_t orig_offset
, void *p
)
360 struct tdr_push
*push
= talloc_zero(regf
, struct tdr_push
);
363 if (NT_STATUS_IS_ERR(push_fn(push
, p
))) {
364 DEBUG(0, ("Error during push\n"));
368 ret
= hbin_store_resize(regf
, orig_offset
, push
->data
);
375 static WERROR
regf_num_subkeys (struct registry_key
*key
, uint32_t *count
)
377 struct nk_block
*nk
= key
->backend_data
;
379 *count
= nk
->num_subkeys
;
384 static WERROR
regf_num_values (struct registry_key
*key
, uint32_t *count
)
386 struct nk_block
*nk
= key
->backend_data
;
388 *count
= nk
->num_values
;
393 static struct registry_key
*regf_get_key (TALLOC_CTX
*ctx
, struct regf_data
*regf
, uint32_t offset
)
395 struct registry_key
*ret
;
398 ret
= talloc_zero(ctx
, struct registry_key
);
399 nk
= talloc(ret
, struct nk_block
);
400 if (!hbin_get_tdr(regf
, offset
, (tdr_pull_fn_t
)tdr_pull_nk_block
, nk
)) {
401 DEBUG(0, ("Unable to find HBIN data for offset %d\n", offset
));
405 if (strcmp(nk
->header
, "nk") != 0) {
406 DEBUG(0, ("Expected nk record, got %s\n", nk
->header
));
411 ret
->name
= talloc_steal(ret
, nk
->key_name
);
412 ret
->last_mod
= nk
->last_change
;
414 if (nk
->clsname_offset
!= -1) {
415 DATA_BLOB data
= hbin_get(regf
, nk
->clsname_offset
);
416 ret
->class_name
= talloc_strndup(ret
, (char*)data
.data
, nk
->clsname_length
);
418 ret
->backend_data
= nk
;
423 static WERROR
regf_get_value (TALLOC_CTX
*ctx
, struct registry_key
*key
, int idx
, struct registry_value
**ret
)
425 struct nk_block
*nk
= key
->backend_data
;
427 struct regf_data
*regf
= key
->hive
->backend_data
;
431 if (idx
>= nk
->num_values
)
432 return WERR_NO_MORE_ITEMS
;
434 data
= hbin_get(regf
, nk
->values_offset
);
436 DEBUG(0, ("Unable to find value list\n"));
437 return WERR_GENERAL_FAILURE
;
440 if (data
.length
< nk
->num_values
* 4) {
441 DEBUG(1, ("Value counts mismatch\n"));
444 vk_offset
= IVAL(data
.data
, idx
* 4);
446 *ret
= talloc_zero(ctx
, struct registry_value
);
450 vk
= talloc(*ret
, struct vk_block
);
454 if (!hbin_get_tdr(regf
, vk_offset
, (tdr_pull_fn_t
)tdr_pull_vk_block
, vk
)) {
455 DEBUG(0, ("Unable to get VK block at %d\n", vk_offset
));
456 return WERR_GENERAL_FAILURE
;
459 (*ret
)->name
= talloc_steal(*ret
, vk
->data_name
);
460 (*ret
)->data_type
= vk
->data_type
;
461 if (vk
->data_length
& 0x80000000) {
462 vk
->data_length
&=~0x80000000;
463 (*ret
)->data
.data
= (uint8_t *)&vk
->data_offset
;
464 (*ret
)->data
.length
= vk
->data_length
;
466 (*ret
)->data
= hbin_get(regf
, vk
->data_offset
);
469 if ((*ret
)->data
.length
< vk
->data_length
) {
470 DEBUG(1, ("Read data less then indicated data length!\n"));
476 static WERROR
regf_get_subkey (TALLOC_CTX
*ctx
, struct registry_key
*key
, int idx
, struct registry_key
**ret
)
479 struct nk_block
*nk
= key
->backend_data
;
482 if (idx
>= nk
->num_subkeys
)
483 return WERR_NO_MORE_ITEMS
;
485 data
= hbin_get(key
->hive
->backend_data
, nk
->subkeys_offset
);
487 DEBUG(0, ("Unable to find subkey list\n"));
488 return WERR_GENERAL_FAILURE
;
491 if (!strncmp((char *)data
.data
, "li", 2)) {
492 DEBUG(4, ("Subkeys in LI list\n"));
494 } else if (!strncmp((char *)data
.data
, "lf", 2)) {
496 struct tdr_pull
*pull
= talloc_zero(ctx
, struct tdr_pull
);
498 DEBUG(10, ("Subkeys in LF list\n"));
501 if (NT_STATUS_IS_ERR(tdr_pull_lf_block(pull
, &lf
))) {
502 DEBUG(0, ("Error parsing LF list\n"));
503 return WERR_GENERAL_FAILURE
;
506 if (lf
.key_count
!= nk
->num_subkeys
) {
507 DEBUG(0, ("Subkey counts don't match\n"));
508 return WERR_GENERAL_FAILURE
;
511 key_off
= lf
.hr
[idx
].nk_off
;
514 } else if (!strncmp((char *)data
.data
, "ri", 2)) {
515 DEBUG(4, ("Subkeys in RI list\n"));
517 } else if (!strncmp((char *)data
.data
, "lh", 2)) {
518 DEBUG(4, ("Subkeys in LH list\n"));
521 DEBUG(0, ("Unknown type for subkey list (0x%04x): %c%c\n", nk
->subkeys_offset
, data
.data
[0], data
.data
[1]));
522 return WERR_GENERAL_FAILURE
;
525 *ret
= regf_get_key (ctx
, key
->hive
->backend_data
, key_off
);
531 static WERROR
regf_set_sec_desc (struct registry_key
*key
, struct security_descriptor
*sec_desc
)
534 return WERR_NOT_SUPPORTED
;
537 static WERROR
regf_get_sec_desc(TALLOC_CTX
*ctx
, struct registry_key
*key
, struct security_descriptor
**sd
)
539 struct nk_block
*nk
= key
->backend_data
;
541 struct regf_data
*regf
= key
->hive
->backend_data
;
544 if (!hbin_get_tdr(regf
, nk
->sk_offset
, (tdr_pull_fn_t
) tdr_pull_sk_block
, &sk
)) {
545 DEBUG(0, ("Unable to find security descriptor\n"));
546 return WERR_GENERAL_FAILURE
;
549 if (strcmp(sk
.header
, "sk") != 0) {
550 DEBUG(0, ("Expected 'sk', got '%s'\n", sk
.header
));
551 return WERR_GENERAL_FAILURE
;
554 *sd
= talloc(ctx
, struct security_descriptor
);
558 data
.data
= sk
.sec_desc
;
559 data
.length
= sk
.rec_size
;
560 if (NT_STATUS_IS_ERR(ndr_pull_struct_blob(&data
, ctx
, *sd
, (ndr_pull_flags_fn_t
)ndr_pull_security_descriptor
))) {
561 DEBUG(0, ("Error parsing security descriptor\n"));
562 return WERR_GENERAL_FAILURE
;
568 static uint32_t lf_add_entry (struct regf_data
*regf
, uint32_t list_offset
, const char *name
, uint32_t key_offset
)
573 /* Add to subkeys list */
574 if (list_offset
== -1) { /* Need to create subkeys list */
579 if (!hbin_get_tdr(regf
, list_offset
, (tdr_pull_fn_t
)tdr_pull_lf_block
, &lf
)) {
580 DEBUG(0, ("Can't get subkeys list\n"));
585 lf
.hr
= talloc_realloc(regf
, lf
.hr
, struct hash_record
, lf
.key_count
+1);
586 lf
.hr
[lf
.key_count
].nk_off
= key_offset
;
587 lf
.hr
[lf
.key_count
].hash
= talloc_strndup(regf
, name
, 4);
590 ret
= hbin_store_tdr_resize(regf
, (tdr_push_fn_t
)tdr_push_lf_block
, list_offset
, &lf
);
595 static WERROR
regf_del_value (struct registry_key
*parent
, const char *name
)
598 return WERR_NOT_SUPPORTED
;
602 static WERROR
regf_del_key (struct registry_key
*parent
, const char *name
)
604 struct nk_block
*nk
= parent
->backend_data
;
608 if (nk
->subkeys_offset
== -1)
613 return WERR_NOT_SUPPORTED
;
616 static WERROR
regf_add_key (TALLOC_CTX
*ctx
, struct registry_key
*parent
, const char *name
, uint32_t access_mask
, struct security_descriptor
*sec_desc
, struct registry_key
**ret
)
618 struct nk_block
*parent_nk
= parent
->backend_data
, nk
;
619 struct regf_data
*regf
= parent
->hive
->backend_data
;
623 nk
.type
= REG_SUB_KEY
;
624 unix_to_nt_time(&nk
.last_change
, time(NULL
));
626 nk
.parent_offset
= 0; /* FIXME */
629 nk
.subkeys_offset
= -1;
630 nk
.unknown_offset
= -1;
633 memset(nk
.unk3
, 0, 5);
634 nk
.clsname_offset
= -1;
635 nk
.clsname_length
= 0;
638 offset
= hbin_store_tdr(regf
, (tdr_push_fn_t
) tdr_push_nk_block
, &nk
);
640 parent_nk
->subkeys_offset
= lf_add_entry(regf
, parent_nk
->subkeys_offset
, name
, nk
.parent_offset
);
642 parent_nk
->num_subkeys
++;
644 hbin_store_tdr_resize(regf
, (tdr_push_fn_t
) tdr_push_nk_block
, nk
.parent_offset
, parent_nk
);
646 *ret
= regf_get_key(ctx
, regf
, offset
);
648 /* FIXME: Set sec desc ! */
652 static WERROR
regf_set_value (struct registry_key
*key
, const char *name
, uint32_t type
, DATA_BLOB data
)
656 return WERR_NOT_SUPPORTED
;
659 static WERROR
regf_save_hbin(struct registry_hive
*hive
, struct hbin_block
*hbin
)
661 struct regf_data
*regf
= hive
->backend_data
;
663 /* go to right offset */
664 if (lseek(regf
->fd
, SEEK_SET
, regf
->header
->data_offset
+ hbin
->offset_from_first
) == -1) {
665 DEBUG(0, ("Error lseeking in regf file\n"));
666 return WERR_GENERAL_FAILURE
;
669 if (NT_STATUS_IS_ERR(tdr_push_to_fd(regf
->fd
, (tdr_push_fn_t
)tdr_push_hbin_block
, hbin
))) {
670 DEBUG(0, ("Error writing HBIN block\n"));
671 return WERR_GENERAL_FAILURE
;
677 static WERROR
nt_open_hive (struct registry_hive
*h
, struct registry_key
**key
)
679 struct regf_data
*regf
;
680 struct regf_hdr
*regf_hdr
;
681 struct tdr_pull
*pull
;
684 regf
= (struct regf_data
*)talloc_zero(h
, struct regf_data
);
685 h
->backend_data
= regf
;
687 DEBUG(5, ("Attempting to load registry file\n"));
690 regf
->fd
= open(h
->location
, O_RDWR
);
692 if (regf
->fd
== -1) {
693 DEBUG(0,("Could not load file: %s, %s\n", h
->location
,
695 return WERR_GENERAL_FAILURE
;
698 pull
= talloc_zero(regf
, struct tdr_pull
);
702 pull
->data
.data
= (uint8_t*)fd_load(regf
->fd
, &pull
->data
.length
, regf
);
704 if (pull
->data
.data
== NULL
) {
705 DEBUG(0, ("Error reading data\n"));
706 return WERR_GENERAL_FAILURE
;
709 regf_hdr
= talloc(regf
, struct regf_hdr
);
710 if (NT_STATUS_IS_ERR(tdr_pull_regf_hdr(pull
, regf_hdr
))) {
711 return WERR_GENERAL_FAILURE
;
714 regf
->header
= regf_hdr
;
716 if (strcmp(regf_hdr
->REGF_ID
, "regf") != 0) {
717 DEBUG(0, ("Unrecognized NT registry header id: %s, %s\n",
718 regf_hdr
->REGF_ID
, h
->location
));
721 DEBUG(1, ("Registry '%s' read. Version %d.%d.%d.%d\n",
722 regf_hdr
->description
, regf_hdr
->version
.major
,
723 regf_hdr
->version
.minor
, regf_hdr
->version
.release
,
724 regf_hdr
->version
.build
));
727 * Validate the header ...
729 if (regf_hdr_checksum(pull
->data
.data
) != regf_hdr
->chksum
) {
730 DEBUG(0, ("Registry file checksum error: %s: %d,%d\n",
731 h
->location
, regf_hdr
->chksum
, regf_hdr_checksum(pull
->data
.data
)));
732 return WERR_GENERAL_FAILURE
;
735 pull
->offset
= 0x1000;
738 /* Read in all hbin blocks */
739 regf
->hbins
= talloc_array(regf
, struct hbin_block
*, 1);
740 regf
->hbins
[0] = NULL
;
742 while (pull
->offset
< pull
->data
.length
) {
743 struct hbin_block
*hbin
= talloc(regf
->hbins
, struct hbin_block
);
745 if (NT_STATUS_IS_ERR(tdr_pull_hbin_block(pull
, hbin
))) {
746 DEBUG(0, ("[%d] Error parsing HBIN block\n", i
));
750 if (strcmp(hbin
->HBIN_ID
, "hbin") != 0) {
751 DEBUG(0, ("[%d] Expected 'hbin', got '%s'\n", i
, hbin
->HBIN_ID
));
755 regf
->hbins
[i
] = hbin
;
757 regf
->hbins
= talloc_realloc(regf
, regf
->hbins
, struct hbin_block
*, i
+2);
758 regf
->hbins
[i
] = NULL
;
761 DEBUG(1, ("%d HBIN blocks read\n", i
));
763 *key
= regf_get_key(h
, regf
, 0x20);
768 static struct hive_operations reg_backend_nt4
= {
770 .open_hive
= nt_open_hive
,
771 .num_subkeys
= regf_num_subkeys
,
772 .num_values
= regf_num_values
,
773 .get_subkey_by_index
= regf_get_subkey
,
774 .get_value_by_index
= regf_get_value
,
775 .key_get_sec_desc
= regf_get_sec_desc
,
776 .key_set_sec_desc
= regf_set_sec_desc
,
777 .add_key
= regf_add_key
,
778 .set_value
= regf_set_value
,
779 .del_key
= regf_del_key
,
780 .del_value
= regf_del_value
,
783 NTSTATUS
registry_nt4_init(void)
785 return registry_register(®_backend_nt4
);