summaryrefslogtreecommitdiff
path: root/source3/sam/gumm_tdb.c
diff options
context:
space:
mode:
Diffstat (limited to 'source3/sam/gumm_tdb.c')
-rw-r--r--source3/sam/gumm_tdb.c891
1 files changed, 761 insertions, 130 deletions
diff --git a/source3/sam/gumm_tdb.c b/source3/sam/gumm_tdb.c
index 52eaab9e17..53ea872a11 100644
--- a/source3/sam/gumm_tdb.c
+++ b/source3/sam/gumm_tdb.c
@@ -23,7 +23,6 @@
*/
#include "includes.h"
-#include "gums.h"
#include "tdbsam2.h"
#include "tdbsam2_parse_info.h"
@@ -31,15 +30,25 @@ static int tdbgumm_debug_level = DBGC_ALL;
#undef DBGC_CLASS
#define DBGC_CLASS tdbgumm_debug_level
-#define TDBSAM_VERSION "20021215"
+#define TDBSAM_VERSION 20021215
#define TDB_FILE_NAME "tdbsam2.tdb"
-#define DOMAINPREFIX "DOMAIN_"
-#define OBJECTPREFIX "OBJECT_"
+#define NAMEPREFIX "NAME_"
#define SIDPREFIX "SID_"
#define PRIVILEGEPREFIX "PRIV_"
#define TDB_FORMAT_STRING "ddB"
+#define TALLOC_CHECK(ptr, err, label) do { if ((ptr) == NULL) { DEBUG(0, ("%s: Out of memory!\n", __FUNCTION__)); err = NT_STATUS_NO_MEMORY; goto label; } } while(0)
+#define SET_OR_FAIL(func, label) do { if (NT_STATUS_IS_ERR(func)) { DEBUG(0, ("%s: Setting gums object data failed!\n", __FUNCTION__)); goto label; } } while(0)
+
+struct tdbsam2_enum_objs {
+ uint32 type;
+ fstring dom_sid;
+ TDB_CONTEXT *db;
+ TDB_DATA key;
+ struct tdbsam2_enum_objs *next;
+};
+
union tdbsam2_data {
struct tdbsam2_domain_data *domain;
struct tdbsam2_user_data *user;
@@ -48,86 +57,363 @@ union tdbsam2_data {
struct tdbsam2_object {
uint32 type;
+ uint32 version;
union tdbsam2_data data;
};
static TDB_CONTEXT *tdbsam2_db;
-#define TALLOC_CHECK(ptr, err, label) do { if ((ptr) == NULL) { DEBUG(0, ("%s: Out of memory!\n", __FUNCTION__)); err = NT_STATUS_NO_MEMORY; goto label; } } while(0)
-#define SET_OR_FAIL(func, label) do { if (NT_STATUS_IS_ERR(func)) { DEBUG(0, ("%s: Setting gums object data failed!\n", __FUNCTION__)); goto label; } } while(0)
+struct tdbsam2_enum_objs **teo_handlers;
+
+static NTSTATUS init_tdbsam2_object_from_buffer(struct tdbsam2_object *object, TALLOC_CTX *mem_ctx, char *buffer, int size)
+{
+
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ int iret;
+ char *obj_data;
+ int data_size = 0;
+ int len;
+
+ len = tdb_unpack (buffer, size, TDB_FORMAT_STRING,
+ &(object->version),
+ &(object->type),
+ &data_size, &obj_data);
+
+ if (len == -1)
+ goto done;
+
+ /* version is checked inside this function so that backward compatibility code can be
+ called eventually.
+ this way we can easily handle database format upgrades */
+ if (object->version != TDBSAM_VERSION) {
+ DEBUG(3,("init_tdbsam2_object_from_buffer: Error, db object has wrong tdbsam version!\n"));
+ goto done;
+ }
+
+ /* be sure the string is terminated before trying to parse it */
+ if (obj_data[data_size - 1] != '\0')
+ obj_data[data_size - 1] = '\0';
+
+ switch (object->type) {
+ case GUMS_OBJ_DOMAIN:
+ object->data.domain = (struct tdbsam2_domain_data *)talloc(mem_ctx, sizeof(struct tdbsam2_domain_data));
+ TALLOC_CHECK(object->data.domain, ret, done);
+ memset(object->data.domain, 0, sizeof(struct tdbsam2_domain_data));
+
+ iret = gen_parse(mem_ctx, pinfo_tdbsam2_domain_data, (char *)(object->data.domain), obj_data);
+ break;
+ case GUMS_OBJ_GROUP:
+ case GUMS_OBJ_ALIAS:
+ object->data.group = (struct tdbsam2_group_data *)talloc(mem_ctx, sizeof(struct tdbsam2_group_data));
+ TALLOC_CHECK(object->data.group, ret, done);
+ memset(object->data.group, 0, sizeof(struct tdbsam2_group_data));
+
+ iret = gen_parse(mem_ctx, pinfo_tdbsam2_group_data, (char *)(object->data.group), obj_data);
+ break;
+ case GUMS_OBJ_NORMAL_USER:
+ object->data.user = (struct tdbsam2_user_data *)talloc(mem_ctx, sizeof(struct tdbsam2_user_data));
+ TALLOC_CHECK(object->data.user, ret, done);
+ memset(object->data.user, 0, sizeof(struct tdbsam2_user_data));
-static NTSTATUS init_tdbsam2_object_from_buffer(struct tdbsam2_object *object, TALLOC_CTX *mem_ctx, char *buffer, int size) {
+ iret = gen_parse(mem_ctx, pinfo_tdbsam2_user_data, (char *)(object->data.user), obj_data);
+ break;
+ default:
+ DEBUG(3,("init_tdbsam2_object_from_buffer: Error, wrong object type number!\n"));
+ goto done;
+ }
+
+ if (iret != 0) {
+ DEBUG(0,("init_tdbsam2_object_from_buffer: Fatal Error! Unable to parse object!\n"));
+ DEBUG(0,("init_tdbsam2_object_from_buffer: DB Corrupted ?"));
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+done:
+ SAFE_FREE(obj_data);
+ return ret;
+}
+
+static NTSTATUS init_buffer_from_tdbsam2_object(char **buffer, size_t *len, TALLOC_CTX *mem_ctx, struct tdbsam2_object *object)
+{
+
+ NTSTATUS ret;
+ char *buf1 = NULL;
+ size_t buflen;
+
+ if (!buffer)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ switch (object->type) {
+ case GUMS_OBJ_DOMAIN:
+ buf1 = gen_dump(mem_ctx, pinfo_tdbsam2_domain_data, (char *)(object->data.domain), 0);
+ break;
+ case GUMS_OBJ_GROUP:
+ case GUMS_OBJ_ALIAS:
+ buf1 = gen_dump(mem_ctx, pinfo_tdbsam2_group_data, (char *)(object->data.group), 0);
+ break;
+ case GUMS_OBJ_NORMAL_USER:
+ buf1 = gen_dump(mem_ctx, pinfo_tdbsam2_user_data, (char *)(object->data.user), 0);
+ break;
+ default:
+ DEBUG(3,("init_buffer_from_tdbsam2_object: Error, wrong object type number!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (buf1 == NULL) {
+ DEBUG(0, ("init_buffer_from_tdbsam2_object: Fatal Error! Unable to dump object!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ buflen = tdb_pack(NULL, 0, TDB_FORMAT_STRING,
+ TDBSAM_VERSION,
+ object->type,
+ strlen(buf1) + 1, buf1);
+
+ *buffer = talloc(mem_ctx, buflen);
+ TALLOC_CHECK(*buffer, ret, done);
+
+ *len = tdb_pack(*buffer, buflen, TDB_FORMAT_STRING,
+ TDBSAM_VERSION,
+ object->type,
+ strlen(buf1) + 1, buf1);
+
+ if (*len != buflen) {
+ DEBUG(0, ("init_tdb_data_from_tdbsam2_object: somthing odd is going on here: bufflen (%d) != len (%d) in tdb_pack operations!\n",
+ buflen, *len));
+ *buffer = NULL;
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+done:
+ return ret;
+}
+
+static NTSTATUS opentdb(void)
+{
+ if (!tdbsam2_db) {
+ pstring tdbfile;
+ get_private_directory(tdbfile);
+ pstrcat(tdbfile, "/");
+ pstrcat(tdbfile, TDB_FILE_NAME);
+
+ tdbsam2_db = tdb_open_log(tdbfile, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0600);
+ if (!tdbsam2_db)
+ {
+ DEBUG(0, ("opentdb: Unable to open database (%s)!\n", tdbfile));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
return NT_STATUS_OK;
}
-static NTSTATUS tdbsam2_opentdb(void) {
+static NTSTATUS get_object_by_sid(TALLOC_CTX *mem_ctx, struct tdbsam2_object *obj, const DOM_SID *sid)
+{
+ NTSTATUS ret;
+ TDB_DATA data, key;
+ fstring keystr;
+
+ if (!obj || !mem_ctx || !sid)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ if (NT_STATUS_IS_ERR(ret = opentdb())) {
+ return ret;
+ }
+
+ slprintf(keystr, sizeof(keystr)-1, "%s%s", SIDPREFIX, sid_string_static(sid));
+ key.dptr = keystr;
+ key.dsize = strlen(keystr) + 1;
+
+ data = tdb_fetch(tdbsam2_db, key);
+ if (!data.dptr) {
+ DEBUG(5, ("get_object_by_sid: Error fetching database, domain entry not found!\n"));
+ DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
+ DEBUGADD(5, (" Key: %s\n", keystr));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (NT_STATUS_IS_ERR(init_tdbsam2_object_from_buffer(obj, mem_ctx, data.dptr, data.dsize))) {
+ SAFE_FREE(data.dptr);
+ DEBUG(0, ("get_object_by_sid: Error fetching database, malformed entry!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ SAFE_FREE(data.dptr);
return NT_STATUS_OK;
+
}
-static NTSTATUS tdbsam2_get_object_by_name(struct tdbsam2_object *obj, TALLOC_CTX *mem_ctx, const char* name) {
+static NTSTATUS get_object_by_name(TALLOC_CTX *mem_ctx, struct tdbsam2_object *obj, const char* name)
+{
NTSTATUS ret;
TDB_DATA data, key;
fstring keystr;
fstring objname;
+ DOM_SID sid;
+ char *obj_sidstr;
+ int obj_version, obj_type, obj_sidstr_len, len;
if (!obj || !mem_ctx || !name)
return NT_STATUS_INVALID_PARAMETER;
- if (tdbsam2_db == NULL) {
- if (NT_STATUS_IS_ERR(ret = tdbsam2_opentdb())) {
- goto done;
- }
+ if (NT_STATUS_IS_ERR(ret = opentdb())) {
+ return ret;
}
unix_strlower(name, -1, objname, sizeof(objname));
- slprintf(keystr, sizeof(keystr)-1, "%s%s", OBJECTPREFIX, objname);
+ slprintf(keystr, sizeof(keystr)-1, "%s%s", NAMEPREFIX, objname);
key.dptr = keystr;
key.dsize = strlen(keystr) + 1;
data = tdb_fetch(tdbsam2_db, key);
if (!data.dptr) {
- DEBUG(5, ("get_domain_sid: Error fetching database, domain entry not found!\n"));
+ DEBUG(5, ("get_object_by_name: Error fetching database, domain entry not found!\n"));
DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
DEBUGADD(5, (" Key: %s\n", keystr));
- ret = NT_STATUS_UNSUCCESSFUL;
- goto done;
+ return NT_STATUS_UNSUCCESSFUL;
}
- if (NT_STATUS_IS_ERR(init_tdbsam2_object_from_buffer(obj, mem_ctx, data.dptr, data.dsize))) {
- SAFE_FREE(data.dptr);
- DEBUG(0, ("get_domain_sid: Error fetching database, malformed entry!\n"));
- ret = NT_STATUS_UNSUCCESSFUL;
- goto done;
- }
+ len = tdb_unpack(data.dptr, data.dsize, TDB_FORMAT_STRING,
+ &obj_version,
+ &obj_type,
+ &obj_sidstr_len, &obj_sidstr);
+
SAFE_FREE(data.dptr);
- ret = NT_STATUS_OK;
+ if (len == -1 || obj_version != TDBSAM_VERSION || obj_sidstr_len <= 0) {
+ DEBUG(5, ("get_object_by_name: Error unpacking database object!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
-done:
- return ret;
+ if (!string_to_sid(&sid, obj_sidstr)) {
+ DEBUG(5, ("get_object_by_name: Error invalid sid string found in database object!\n"));
+ SAFE_FREE(obj_sidstr);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ SAFE_FREE(obj_sidstr);
+
+ return get_object_by_sid(mem_ctx, obj, &sid);
}
-
-static NTSTATUS tdbsam2_store(struct tdbsam2_object *object) {
+static NTSTATUS store_object(TALLOC_CTX *mem_ctx, struct tdbsam2_object *object, BOOL new_obj)
+{
NTSTATUS ret;
+ TDB_DATA data, key, key2;
+ fstring keystr;
+ fstring namestr;
+ int flag, r;
+
+ if (NT_STATUS_IS_ERR(ret = opentdb())) {
+ return ret;
+ }
+
+ if (new_obj) {
+ flag = TDB_INSERT;
+ } else {
+ flag = TDB_MODIFY;
+ }
+
+ ret = init_buffer_from_tdbsam2_object(&(data.dptr), &(data.dsize), mem_ctx, object);
+ if (NT_STATUS_IS_ERR(ret))
+ return ret;
+
+ switch (object->type) {
+ case GUMS_OBJ_DOMAIN:
+ slprintf(keystr, sizeof(keystr) - 1, "%s%s", SIDPREFIX, sid_string_static(object->data.domain->dom_sid));
+ slprintf(namestr, sizeof(namestr) - 1, "%s%s", NAMEPREFIX, object->data.domain->name);
+ break;
+ case GUMS_OBJ_GROUP:
+ case GUMS_OBJ_ALIAS:
+ slprintf(keystr, sizeof(keystr) - 1, "%s%s", SIDPREFIX, sid_string_static(object->data.group->group_sid));
+ slprintf(namestr, sizeof(namestr) - 1, "%s%s", NAMEPREFIX, object->data.group->name);
+ break;
+ case GUMS_OBJ_NORMAL_USER:
+ slprintf(keystr, sizeof(keystr) - 1, "%s%s", SIDPREFIX, sid_string_static(object->data.user->user_sid));
+ slprintf(namestr, sizeof(namestr) - 1, "%s%s", NAMEPREFIX, object->data.user->name);
+ break;
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ key.dptr = keystr;
+ key.dsize = strlen(keystr) + 1;
+
+ if ((r = tdb_store(tdbsam2_db, key, data, flag)) != TDB_SUCCESS) {
+ DEBUG(0, ("store_object: Unable to modify SAM!\n"));
+ DEBUGADD(0, (" Error: %s", tdb_errorstr(tdbsam2_db)));
+ DEBUGADD(0, (" occured while storing the main record (%s)\n", keystr));
+ if (r == TDB_ERR_EXISTS) return NT_STATUS_UNSUCCESSFUL;
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ key2.dptr = namestr;
+ key2.dsize = strlen(namestr) + 1;
+
+ if ((r = tdb_store(tdbsam2_db, key2, key, flag)) != TDB_SUCCESS) {
+ DEBUG(0, ("store_object: Unable to modify SAM!\n"));
+ DEBUGADD(0, (" Error: %s", tdb_errorstr(tdbsam2_db)));
+ DEBUGADD(0, (" occured while storing the main record (%s)\n", keystr));
+ if (r == TDB_ERR_EXISTS) return NT_STATUS_UNSUCCESSFUL;
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+/* TODO: update the general database counter */
+/* TODO: update this entry counter too */
return NT_STATUS_OK;
}
-static NTSTATUS tdbsam2_get_next_sid(TALLOC_CTX *mem_ctx, DOM_SID *sid) {
-
+static NTSTATUS get_next_sid(TALLOC_CTX *mem_ctx, DOM_SID **sid)
+{
NTSTATUS ret;
+ struct tdbsam2_object obj;
+ DOM_SID *dom_sid = get_global_sam_sid();
+ uint32 new_rid;
+
+/* TODO: LOCK DOMAIN OBJECT */
+ ret = get_object_by_sid(mem_ctx, &obj, dom_sid);
+ if (NT_STATUS_IS_ERR(ret)) {
+ DEBUG(0, ("get_next_sid: unable to get root Domain object!\n"));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto error;
+ }
+
+ new_rid = obj.data.domain->next_rid;
+
+ /* Increment the RID Counter */
+ obj.data.domain->next_rid++;
+
+ /* Store back Domain object */
+ ret = store_object(mem_ctx, &obj, False);
+ if (NT_STATUS_IS_ERR(ret)) {
+ DEBUG(0, ("get_next_sid: unable to update root Domain object!\n"));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto error;
+ }
+/* TODO: UNLOCK DOMAIN OBJECT */
+
+ *sid = sid_dup_talloc(mem_ctx, dom_sid);
+ TALLOC_CHECK(*sid, ret, error);
+
+ if (!sid_append_rid(*sid, new_rid)) {
+ DEBUG(0, ("get_next_sid: unable to build new SID !?!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto error;
+ }
return NT_STATUS_OK;
-}
-static NTSTATUS tdbsam2_user_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_user_data *userdata, uint32 type) {
+error:
+ return ret;
+}
+static NTSTATUS user_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_user_data *userdata)
+{
NTSTATUS ret;
if (!object || !userdata) {
@@ -180,12 +466,12 @@ static NTSTATUS tdbsam2_user_data_to_gums_object(GUMS_OBJECT **object, struct td
SET_OR_FAIL(gums_set_user_unknown_5(*object, userdata->unknown_5), error);
SET_OR_FAIL(gums_set_user_unknown_6(*object, userdata->unknown_6), error);
- SET_OR_FAIL(gums_set_user_logon_time(*object, userdata->logon_time), error);
- SET_OR_FAIL(gums_set_user_logoff_time(*object, userdata->logoff_time), error);
- SET_OR_FAIL(gums_set_user_kickoff_time(*object, userdata->kickoff_time), error);
- SET_OR_FAIL(gums_set_user_pass_last_set_time(*object, userdata->pass_last_set_time), error);
- SET_OR_FAIL(gums_set_user_pass_can_change_time(*object, userdata->pass_can_change_time), error);
- SET_OR_FAIL(gums_set_user_pass_must_change_time(*object, userdata->pass_must_change_time), error);
+ SET_OR_FAIL(gums_set_user_logon_time(*object, *(userdata->logon_time)), error);
+ SET_OR_FAIL(gums_set_user_logoff_time(*object, *(userdata->logoff_time)), error);
+ SET_OR_FAIL(gums_set_user_kickoff_time(*object, *(userdata->kickoff_time)), error);
+ SET_OR_FAIL(gums_set_user_pass_last_set_time(*object, *(userdata->pass_last_set_time)), error);
+ SET_OR_FAIL(gums_set_user_pass_can_change_time(*object, *(userdata->pass_can_change_time)), error);
+ SET_OR_FAIL(gums_set_user_pass_must_change_time(*object, *(userdata->pass_must_change_time)), error);
ret = NT_STATUS_OK;
return ret;
@@ -196,8 +482,8 @@ error:
return ret;
}
-static NTSTATUS tdbsam2_group_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_group_data *groupdata, uint32 type) {
-
+static NTSTATUS group_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_group_data *groupdata)
+{
NTSTATUS ret;
if (!object || !groupdata) {
@@ -226,11 +512,12 @@ error:
return ret;
}
-static NTSTATUS tdbsam2_domain_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_domain_data *domdata, uint32 type) {
+static NTSTATUS domain_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_domain_data *domdata)
+{
NTSTATUS ret;
- if (!object || !domdata) {
+ if (!object || !*object || !domdata) {
DEBUG(0, ("tdbsam2_domain_data_to_gums_object: no NULL pointers are accepted here!\n"));
return NT_STATUS_UNSUCCESSFUL;
}
@@ -253,7 +540,8 @@ error:
return ret;
}
-static NTSTATUS tdbsam2_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_object *data) {
+static NTSTATUS data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2_object *data)
+{
NTSTATUS ret;
@@ -271,16 +559,16 @@ static NTSTATUS tdbsam2_data_to_gums_object(GUMS_OBJECT **object, struct tdbsam2
switch (data->type) {
case GUMS_OBJ_DOMAIN:
- ret = tdbsam2_domain_data_to_gums_object(object, data->data.domain, data->type);
+ ret = domain_data_to_gums_object(object, data->data.domain);
break;
case GUMS_OBJ_NORMAL_USER:
- ret = tdbsam2_user_data_to_gums_object(object, data->data.user, data->type);
+ ret = user_data_to_gums_object(object, data->data.user);
break;
case GUMS_OBJ_GROUP:
case GUMS_OBJ_ALIAS:
- ret = tdbsam2_group_data_to_gums_object(object, data->data.group, data->type);
+ ret = group_data_to_gums_object(object, data->data.group);
break;
default:
@@ -292,83 +580,118 @@ done:
}
+/* GUMM object functions */
+static NTSTATUS tdbsam2_get_domain_sid(DOM_SID *sid, const char* name)
+{
+ NTSTATUS ret;
+ struct tdbsam2_object obj;
+ TALLOC_CTX *mem_ctx;
+ fstring domname;
-/* GUMM object functions */
+ if (!sid || !name)
+ return NT_STATUS_INVALID_PARAMETER;
-static NTSTATUS get_domain_sid(DOM_SID *sid, const char* name) {
+ mem_ctx = talloc_init("tdbsam2_get_domain_sid");
+ if (!mem_ctx) {
+ DEBUG(0, ("tdbsam2_new_object: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_ERR(ret = opentdb())) {
+ goto done;
+ }
+
+ unix_strlower(name, -1, domname, sizeof(domname));
+
+ ret = get_object_by_name(mem_ctx, &obj, domname);
+
+ if (NT_STATUS_IS_ERR(ret)) {
+ DEBUG(0, ("tdbsam2_get_domain_sid: Error fetching database!\n"));
+ goto done;
+ }
+
+ if (obj.type != GUMS_OBJ_DOMAIN) {
+ DEBUG(5, ("tdbsam2_get_domain_sid: Requested object is not a domain!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ sid_copy(sid, obj.data.domain->dom_sid);
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_destroy(mem_ctx);
+ return ret;
+}
+
+static NTSTATUS tdbsam2_set_domain_sid (const DOM_SID *sid, const char *name)
+{
NTSTATUS ret;
struct tdbsam2_object obj;
TALLOC_CTX *mem_ctx;
- TDB_DATA data, key;
- fstring keystr;
fstring domname;
if (!sid || !name)
return NT_STATUS_INVALID_PARAMETER;
- mem_ctx = talloc_init("get_domain_sid");
+ mem_ctx = talloc_init("tdbsam2_set_domain_sid");
if (!mem_ctx) {
DEBUG(0, ("tdbsam2_new_object: Out of memory!\n"));
return NT_STATUS_NO_MEMORY;
}
if (tdbsam2_db == NULL) {
- if (NT_STATUS_IS_ERR(ret = tdbsam2_opentdb())) {
+ if (NT_STATUS_IS_ERR(ret = opentdb())) {
goto done;
}
}
unix_strlower(name, -1, domname, sizeof(domname));
- slprintf(keystr, sizeof(keystr)-1, "%s%s", DOMAINPREFIX, domname);
- key.dptr = keystr;
- key.dsize = strlen(keystr) + 1;
+/* TODO: we need to lock this entry until updated! */
- data = tdb_fetch(tdbsam2_db, key);
- if (!data.dptr) {
- DEBUG(5, ("get_domain_sid: Error fetching database, domain entry not found!\n"));
- DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
- DEBUGADD(5, (" Key: %s\n", keystr));
- ret = NT_STATUS_UNSUCCESSFUL;
- goto done;
- }
+ ret = get_object_by_name(mem_ctx, &obj, domname);
- if (NT_STATUS_IS_ERR(init_tdbsam2_object_from_buffer(&obj, mem_ctx, data.dptr, data.dsize))) {
- SAFE_FREE(data.dptr);
- DEBUG(0, ("get_domain_sid: Error fetching database, malformed entry!\n"));
- ret = NT_STATUS_UNSUCCESSFUL;
+ if (NT_STATUS_IS_ERR(ret)) {
+ DEBUG(0, ("tdbsam2_get_domain_sid: Error fetching database!\n"));
goto done;
}
- SAFE_FREE(data.dptr);
if (obj.type != GUMS_OBJ_DOMAIN) {
- DEBUG(5, ("get_domain_sid: Requested object is not a domain!\n"));
+ DEBUG(5, ("tdbsam2_get_domain_sid: Requested object is not a domain!\n"));
ret = NT_STATUS_UNSUCCESSFUL;
goto done;
}
- sid_copy(sid, obj.data.domain->dom_sid);
+ sid_copy(obj.data.domain->dom_sid, sid);
- ret = NT_STATUS_OK;
+ ret = store_object(mem_ctx, &obj, False);
done:
+/* TODO: unlock here */
if (mem_ctx) talloc_destroy(mem_ctx);
return ret;
}
- NTSTATUS (*set_domain_sid) (const DOM_SID *sid, const char *name);
-
+/* TODO */
NTSTATUS (*get_sequence_number) (void);
-static NTSTATUS tdbsam2_new_object(DOM_SID **sid, const char *name, const int obj_type) {
+extern DOM_SID global_sid_NULL;
+
+static NTSTATUS tdbsam2_new_object(DOM_SID *sid, const char *name, const int obj_type)
+{
NTSTATUS ret;
struct tdbsam2_object obj;
TALLOC_CTX *mem_ctx;
+ NTTIME zero_time = {0,0};
+ const char *defpw = "NOPASSWORDXXXXXX";
+ uint8 defhours[21] = {255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255};
if (!sid || !name) {
DEBUG(0, ("tdbsam2_new_object: no NULL pointers are accepted here!\n"));
@@ -381,19 +704,51 @@ static NTSTATUS tdbsam2_new_object(DOM_SID **sid, const char *name, const int ob
return NT_STATUS_NO_MEMORY;
}
+ obj.type = obj_type;
+ obj.version = TDBSAM_VERSION;
+
switch (obj_type) {
case GUMS_OBJ_NORMAL_USER:
obj.data.user = (struct tdbsam2_user_data *)talloc_zero(mem_ctx, sizeof(struct tdbsam2_user_data));
TALLOC_CHECK(obj.data.user, ret, done);
- /*obj.data.user->sec_desc*/
-
- tdbsam2_get_next_sid(mem_ctx, obj.data.user->user_sid);
+ get_next_sid(mem_ctx, &(obj.data.user->user_sid));
TALLOC_CHECK(obj.data.user->user_sid, ret, done);
+ sid_copy(sid, obj.data.user->user_sid);
obj.data.user->name = talloc_strdup(mem_ctx, name);
TALLOC_CHECK(obj.data.user, ret, done);
+ obj.data.user->xcounter = 1;
+ /*obj.data.user->sec_desc*/
+ obj.data.user->description = "";
+ obj.data.user->group_sid = &global_sid_NULL;
+ obj.data.user->logon_time = &zero_time;
+ obj.data.user->logoff_time = &zero_time;
+ obj.data.user->kickoff_time = &zero_time;
+ obj.data.user->pass_last_set_time = &zero_time;
+ obj.data.user->pass_can_change_time = &zero_time;
+ obj.data.user->pass_must_change_time = &zero_time;
+
+ obj.data.user->full_name = "";
+ obj.data.user->home_dir = "";
+ obj.data.user->dir_drive = "";
+ obj.data.user->logon_script = "";
+ obj.data.user->profile_path = "";
+ obj.data.user->workstations = "";
+ obj.data.user->unknown_str = "";
+ obj.data.user->munged_dial = "";
+
+ obj.data.user->lm_pw_ptr = defpw;
+ obj.data.user->nt_pw_ptr = defpw;
+
+ obj.data.user->logon_divs = 168;
+ obj.data.user->hours_len = 21;
+ obj.data.user->hours = &defhours;
+
+ obj.data.user->unknown_3 = 0x00ffffff;
+ obj.data.user->unknown_5 = 0x00020000;
+ obj.data.user->unknown_6 = 0x000004ec;
break;
case GUMS_OBJ_GROUP:
@@ -401,133 +756,373 @@ static NTSTATUS tdbsam2_new_object(DOM_SID **sid, const char *name, const int ob
obj.data.group = (struct tdbsam2_group_data *)talloc_zero(mem_ctx, sizeof(struct tdbsam2_group_data));
TALLOC_CHECK(obj.data.group, ret, done);
- /*obj.data.user->sec_desc*/
-
- tdbsam2_get_next_sid(mem_ctx, obj.data.group->group_sid);
+ get_next_sid(mem_ctx, &(obj.data.group->group_sid));
TALLOC_CHECK(obj.data.group->group_sid, ret, done);
+ sid_copy(sid, obj.data.group->group_sid);
obj.data.group->name = talloc_strdup(mem_ctx, name);
TALLOC_CHECK(obj.data.group, ret, done);
+ obj.data.group->xcounter = 1;
+ /*obj.data.group->sec_desc*/
+ obj.data.group->description = "";
+
break;
case GUMS_OBJ_DOMAIN:
- /* TODO: SHOULD WE ALLOW TO CREATE NEW DOMAINS ? */
+
+ /* FIXME: should we check against global_sam_sid to make it impossible
+ to store more than one domain ? */
+
+ obj.data.domain = (struct tdbsam2_domain_data *)talloc_zero(mem_ctx, sizeof(struct tdbsam2_domain_data));
+ TALLOC_CHECK(obj.data.domain, ret, done);
+
+ obj.data.domain->dom_sid = sid_dup_talloc(mem_ctx, get_global_sam_sid());
+ TALLOC_CHECK(obj.data.domain->dom_sid, ret, done);
+ sid_copy(sid, obj.data.domain->dom_sid);
+
+ obj.data.domain->name = talloc_strdup(mem_ctx, name);
+ TALLOC_CHECK(obj.data.domain, ret, done);
+
+ obj.data.domain->xcounter = 1;
+ /*obj.data.domain->sec_desc*/
+ obj.data.domain->next_rid = 0x3e9;
+ obj.data.domain->description = "";
+
+ ret = NT_STATUS_OK;
+ break;
default:
ret = NT_STATUS_UNSUCCESSFUL;
goto done;
}
- ret = tdbsam2_store(&obj);
+ ret = store_object(mem_ctx, &obj, True);
done:
talloc_destroy(mem_ctx);
return ret;
}
-static NTSTATUS tdbsam2_delete_object(const DOM_SID *sid) {
-
+static NTSTATUS tdbsam2_delete_object(const DOM_SID *sid)
+{
NTSTATUS ret;
struct tdbsam2_object obj;
TALLOC_CTX *mem_ctx;
TDB_DATA data, key;
fstring keystr;
- fstring sidstr;
- char *obj_name = NULL;
- int obj_type, obj_version, len;
if (!sid) {
- DEBUG(0, ("tdbsam2_new_object: no NULL pointers are accepted here!\n"));
+ DEBUG(0, ("tdbsam2_delete_object: no NULL pointers are accepted here!\n"));
return NT_STATUS_INVALID_PARAMETER;
}
mem_ctx = talloc_init("tdbsam2_delete_object");
if (!mem_ctx) {
- DEBUG(0, ("tdbsam2_new_object: Out of memory!\n"));
+ DEBUG(0, ("tdbsam2_delete_object: Out of memory!\n"));
return NT_STATUS_NO_MEMORY;
}
if (tdbsam2_db == NULL) {
- if (NT_STATUS_IS_ERR(ret = tdbsam2_opentdb())) {
+ if (NT_STATUS_IS_ERR(ret = opentdb())) {
goto done;
}
}
- sid_to_string(sidstr, sid);
-
- slprintf(keystr, sizeof(keystr)-1, "%s%s", SIDPREFIX, sidstr);
+ slprintf(keystr, sizeof(keystr)-1, "%s%s", SIDPREFIX, sid_string_static(sid));
key.dptr = keystr;
key.dsize = strlen(keystr) + 1;
data = tdb_fetch(tdbsam2_db, key);
if (!data.dptr) {
- DEBUG(5, ("get_domain_sid: Error fetching database, SID entry not found!\n"));
+ DEBUG(5, ("tdbsam2_delete_object: Error fetching database, SID entry not found!\n"));
DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
DEBUGADD(5, (" Key: %s\n", keystr));
ret = NT_STATUS_UNSUCCESSFUL;
goto done;
}
- len = tdb_unpack(data.dptr, data.dsize, TDB_FORMAT_STRING,
- &obj_version,
- &obj_type,
- &obj_name);
+ if (tdb_delete(tdbsam2_db, key) != TDB_SUCCESS) {
+ DEBUG(5, ("tdbsam2_delete_object: Error deleting object!\n"));
+ DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
+ DEBUGADD(5, (" Key: %s\n", keystr));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
- if (len == -1) {
+ if (NT_STATUS_IS_ERR(init_tdbsam2_object_from_buffer(&obj, mem_ctx, data.dptr, data.dsize))) {
+ SAFE_FREE(data.dptr);
+ DEBUG(0, ("tdbsam2_delete_object: Error fetching database, malformed entry!\n"));
ret = NT_STATUS_UNSUCCESSFUL;
goto done;
}
+ switch (obj.type) {
+ case GUMS_OBJ_DOMAIN:
+ /* TODO: SHOULD WE ALLOW TO DELETE DOMAINS ? */
+ slprintf(keystr, sizeof(keystr) - 1, "%s%s", NAMEPREFIX, obj.data.domain->name);
+ break;
+ case GUMS_OBJ_GROUP:
+ case GUMS_OBJ_ALIAS:
+ slprintf(keystr, sizeof(keystr) - 1, "%s%s", NAMEPREFIX, obj.data.group->name);
+ break;
+ case GUMS_OBJ_NORMAL_USER:
+ slprintf(keystr, sizeof(keystr) - 1, "%s%s", NAMEPREFIX, obj.data.user->name);
+ break;
+ default:
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ key.dptr = keystr;
+ key.dsize = strlen(keystr) + 1;
+
if (tdb_delete(tdbsam2_db, key) != TDB_SUCCESS) {
- DEBUG(5, ("tdbsam2_object_delete: Error deleting object!\n"));
+ DEBUG(5, ("tdbsam2_delete_object: Error deleting object!\n"));
DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
DEBUGADD(5, (" Key: %s\n", keystr));
ret = NT_STATUS_UNSUCCESSFUL;
goto done;
- }
+ }
- switch (obj_type) {
- case GUMS_OBJ_NORMAL_USER:
- case GUMS_OBJ_GROUP:
- case GUMS_OBJ_ALIAS:
-
- slprintf(keystr, sizeof(keystr)-1, "%s%s", OBJECTPREFIX, obj_name);
- key.dptr = keystr;
- key.dsize = strlen(keystr) + 1;
-
- if (tdb_delete(tdbsam2_db, key) != TDB_SUCCESS) {
- DEBUG(5, ("tdbsam2_object_delete: Error deleting object!\n"));
- DEBUGADD(5, (" Error: %s\n", tdb_errorstr(tdbsam2_db)));
- DEBUGADD(5, (" Key: %s\n", keystr));
- ret = NT_STATUS_UNSUCCESSFUL;
- goto done;
+/* TODO: update the general database counter */
+
+done:
+ SAFE_FREE(data.dptr);
+ talloc_destroy(mem_ctx);
+ return ret;
+}
+
+static NTSTATUS tdbsam2_get_object_from_sid(GUMS_OBJECT **object, const DOM_SID *sid, const int obj_type)
+{
+ NTSTATUS ret;
+ struct tdbsam2_object obj;
+ TALLOC_CTX *mem_ctx;
+
+ if (!object || !sid) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: no NULL pointers are accepted here!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ mem_ctx = talloc_init("tdbsam2_get_object_from_sid");
+ if (!mem_ctx) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = get_object_by_sid(mem_ctx, &obj, sid);
+ if (NT_STATUS_IS_ERR(ret) || (obj_type && obj.type != obj_type)) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: error fetching object or wrong object type!\n"));
+ goto done;
+ }
+
+ ret = data_to_gums_object(object, &obj);
+ if (NT_STATUS_IS_ERR(ret)) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: error setting object data!\n"));
+ goto done;
+ }
+
+done:
+ talloc_destroy(mem_ctx);
+ return ret;
+}
+
+static NTSTATUS tdbsam2_get_object_from_name(GUMS_OBJECT **object, const char *name, const int obj_type)
+{
+ NTSTATUS ret;
+ struct tdbsam2_object obj;
+ TALLOC_CTX *mem_ctx;
+
+ if (!object || !name) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: no NULL pointers are accepted here!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ mem_ctx = talloc_init("tdbsam2_get_object_from_sid");
+ if (!mem_ctx) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = get_object_by_name(mem_ctx, &obj, name);
+ if (NT_STATUS_IS_ERR(ret) || (obj_type && obj.type != obj_type)) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: error fetching object or wrong object type!\n"));
+ goto done;
+ }
+
+ ret = data_to_gums_object(object, &obj);
+ if (NT_STATUS_IS_ERR(ret)) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: error setting object data!\n"));
+ goto done;
+ }
+
+done:
+ talloc_destroy(mem_ctx);
+ return ret;
+}
+
+ /* This function is used to get the list of all objects changed since base_time, it is
+ used to support PDC<->BDC synchronization */
+ NTSTATUS (*get_updated_objects) (GUMS_OBJECT **objects, const NTTIME base_time);
+
+static NTSTATUS tdbsam2_enumerate_objects_start(void *handle, const DOM_SID *sid, const int obj_type)
+{
+ struct tdbsam2_enum_objs *teo, *t;
+ pstring tdbfile;
+
+ teo = (struct tdbsam2_enum_objs *)calloc(1, sizeof(struct tdbsam2_enum_objs));
+ if (!teo) {
+ DEBUG(0, ("tdbsam2_enumerate_objects_start: Out of Memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ teo->type = obj_type;
+ if (sid) {
+ sid_to_string(teo->dom_sid, sid);
+ }
+
+ get_private_directory(tdbfile);
+ pstrcat(tdbfile, "/");
+ pstrcat(tdbfile, TDB_FILE_NAME);
+
+ teo->db = tdb_open_log(tdbfile, 0, TDB_DEFAULT, O_RDONLY, 0600);
+ if (!teo->db)
+ {
+ DEBUG(0, ("tdbsam2_enumerate_objects_start: Unable to open database (%s)!\n", tdbfile));
+ SAFE_FREE(teo);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!teo_handlers) {
+ *teo_handlers = teo;
+ } else {
+ t = *teo_handlers;
+ while (t->next) {
+ t = t->next;
+ }
+ t->next = teo;
+ }
+
+ handle = teo;
+
+ teo->key = tdb_firstkey(teo->db);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS tdbsam2_enumerate_objects_get_next(GUMS_OBJECT **object, void *handle)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *mem_ctx;
+ TDB_DATA data;
+ struct tdbsam2_enum_objs *teo;
+ struct tdbsam2_object obj;
+ const char *prefix = SIDPREFIX;
+ const int preflen = strlen(prefix);
+
+ if (!object || !handle) {
+ DEBUG(0, ("tdbsam2_get_object_from_sid: no NULL pointers are accepted here!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ teo = (struct tdbsam2_enum_objs *)handle;
+
+ mem_ctx = talloc_init("tdbsam2_enumerate_objects_get_next");
+ if (!mem_ctx) {
+ DEBUG(0, ("tdbsam2_enumerate_objects_get_next: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ while ((teo->key.dsize != 0)) {
+ int len, version, type, size;
+ char *ptr;
+
+ if (strncmp(teo->key.dptr, prefix, preflen)) {
+ teo->key = tdb_nextkey(teo->db, teo->key);
+ continue;
+ }
+
+ if (teo->dom_sid) {
+ if (strncmp(&(teo->key.dptr[preflen]), teo->dom_sid, strlen(teo->dom_sid))) {
+ teo->key = tdb_nextkey(teo->db, teo->key);
+ continue;
}
- break;
+ }
- case GUMS_OBJ_DOMAIN:
- /* TODO: SHOULD WE ALLOW TO DELETE DOMAINS ? */
+ data = tdb_fetch(teo->db, teo->key);
+ if (!data.dptr) {
+ DEBUG(5, ("tdbsam2_enumerate_objects_get_next: Error fetching database, SID entry not found!\n"));
+ DEBUGADD(5, (" Error: %s\n", tdb_errorstr(teo->db)));
+ DEBUGADD(5, (" Key: %s\n", teo->key.dptr));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
- default:
+ len = tdb_unpack (data.dptr, data.dsize, TDB_FORMAT_STRING,
+ &version,
+ &type,
+ &size, &ptr);
+
+ if (len == -1) {
+ DEBUG(5, ("tdbsam2_enumerate_objects_get_next: Error unable to unpack data!\n"));
ret = NT_STATUS_UNSUCCESSFUL;
goto done;
+ }
+ SAFE_FREE(ptr);
+
+ if (teo->type && type != teo->type) {
+ SAFE_FREE(data.dptr);
+ data.dsize = 0;
+ teo->key = tdb_nextkey(teo->db, teo->key);
+ continue;
+ }
+
+ break;
}
+ if (data.dsize != 0) {
+ if (NT_STATUS_IS_ERR(init_tdbsam2_object_from_buffer(&obj, mem_ctx, data.dptr, data.dsize))) {
+ SAFE_FREE(data.dptr);
+ DEBUG(0, ("tdbsam2_enumerate_objects_get_next: Error fetching database, malformed entry!\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ SAFE_FREE(data.dptr);
+ }
+
+ ret = data_to_gums_object(object, &obj);
+
done:
- SAFE_FREE(obj_name);
talloc_destroy(mem_ctx);
return ret;
}
- NTSTATUS (*get_object_from_sid) (GUMS_OBJECT **object, const DOM_SID *sid, const int obj_type);
- NTSTATUS (*get_sid_from_name) (GUMS_OBJECT **object, const char *name);
- /* This function is used to get the list of all objects changed since b_time, it is
- used to support PDC<->BDC synchronization */
- NTSTATUS (*get_updated_objects) (GUMS_OBJECT **objects, const NTTIME base_time);
+static NTSTATUS tdbsam2_enumerate_objects_stop(void *handle)
+{
+ struct tdbsam2_enum_objs *teo, *t, *p;
+
+ teo = (struct tdbsam2_enum_objs *)handle;
+
+ if (*teo_handlers == teo) {
+ *teo_handlers = teo->next;
+ } else {
+ t = *teo_handlers;
+ while (t != teo) {
+ p = t;
+ t = t->next;
+ if (t == NULL) {
+ DEBUG(0, ("tdbsam2_enumerate_objects_stop: Error, handle not found!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ p = t->next;
+ }
+
+ tdb_close(teo->db);
+ SAFE_FREE(teo);
- NTSTATUS (*enumerate_objects_start) (void *handle, const DOM_SID *sid, const int obj_type);
- NTSTATUS (*enumerate_objects_get_next) (GUMS_OBJECT **object, void *handle);
- NTSTATUS (*enumerate_objects_stop) (void *handle);
+ return NT_STATUS_OK;
+}
/* This function MUST be used ONLY by PDC<->BDC replication code or recovery tools.
Never use this function to update an object in the database, use set_object_values() */
@@ -556,7 +1151,43 @@ done:
NTSTATUS (*set_privilege) (GUMS_PRIVILEGE *priv);
-int gumm_init(GUMS_FUNCTIONS **storage) {
+int gumm_init(GUMS_FUNCTIONS **storage)
+{
+ tdbsam2_db = NULL;
+ teo_handlers = 0;
return 0;
}
+
+#if 0
+int main(int argc, char *argv[])
+{
+ NTSTATUS ret;
+ DOM_SID dsid;
+
+ if (argc < 2) {
+ printf ("not enough arguments!\n");
+ exit(0);
+ }
+
+ if (!lp_load(dyn_CONFIGFILE,True,False,False)) {
+ fprintf(stderr, "Can't load %s - run testparm to debug it\n", dyn_CONFIGFILE);
+ exit(1);
+ }
+
+ ret = tdbsam2_new_object(&dsid, "_domain_", GUMS_OBJ_DOMAIN);
+ if (NT_STATUS_IS_OK(ret)) {
+ printf ("_domain_ created, sid=%s\n", sid_string_static(&dsid));
+ } else {
+ printf ("_domain_ creation error n. 0x%08x\n", ret.v);
+ }
+ ret = tdbsam2_new_object(&dsid, argv[1], GUMS_OBJ_NORMAL_USER);
+ if (NT_STATUS_IS_OK(ret)) {
+ printf ("%s user created, sid=%s\n", argv[1], sid_string_static(&dsid));
+ } else {
+ printf ("%s user creation error n. 0x%08x\n", argv[1], ret.v);
+ }
+
+ exit(0);
+}
+#endif