summaryrefslogtreecommitdiff
path: root/source4/rpc_server/samr
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2004-05-15 07:51:38 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 12:53:46 -0500
commit064e7447bebd715c8351d9a0ee31f648990f2336 (patch)
tree156925cd7c8d4616f0eca3a743b7323b3b0b23b7 /source4/rpc_server/samr
parent31b9470996632d717c3c74482308e200906fdb8f (diff)
downloadsamba-064e7447bebd715c8351d9a0ee31f648990f2336.tar.gz
samba-064e7447bebd715c8351d9a0ee31f648990f2336.tar.bz2
samba-064e7447bebd715c8351d9a0ee31f648990f2336.zip
r743: Start on a NETLOGON server in Samba4.
Currently this only authentiates the machine, not real users. As a consequence of running the Samba4 NETLOGON test against Samba4, I found a number of issues in the SAMR server, which I have addressed. There are more templates in the provison.ldif for this reason. I also added some debug to our credentials code, and fixed some bugs in the auth_sam module. The static buffer in generate_random_string() bit me badly, so I removed it in favor of a talloc based system. Andrew Bartlett (This used to be commit 94624e519b66def97758b8a48a01ffe9029176f0)
Diffstat (limited to 'source4/rpc_server/samr')
-rw-r--r--source4/rpc_server/samr/dcesrv_samr.c77
-rw-r--r--source4/rpc_server/samr/samdb.c202
-rw-r--r--source4/rpc_server/samr/samr_utils.c134
3 files changed, 308 insertions, 105 deletions
diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c
index d5a028ce09..847b30e71c 100644
--- a/source4/rpc_server/samr/dcesrv_samr.c
+++ b/source4/rpc_server/samr/dcesrv_samr.c
@@ -603,6 +603,7 @@ static NTSTATUS samr_CreateUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX
struct dcesrv_handle *u_handle;
int ret;
NTSTATUS status;
+ const char *container;
ZERO_STRUCTP(r->out.acct_handle);
*r->out.access_granted = 0;
@@ -628,14 +629,55 @@ static NTSTATUS samr_CreateUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX
ZERO_STRUCT(msg);
- /* pull in all the template attributes */
- ret = samdb_copy_template(d_state->sam_ctx, mem_ctx, &msg,
- "(&(name=TemplateUser)(objectclass=userTemplate))");
- if (ret != 0) {
- DEBUG(1,("Failed to load TemplateUser from samdb\n"));
- return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ /* This must be one of these values *only* */
+ if (r->in.acct_flags == ACB_NORMAL) {
+ /* pull in all the template attributes */
+ ret = samdb_copy_template(d_state->sam_ctx, mem_ctx, &msg,
+ "(&(name=TemplateUser)(objectclass=userTemplate))");
+ if (ret != 0) {
+ DEBUG(1,("Failed to load TemplateUser from samdb\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ container = "Users";
+
+ } else if (r->in.acct_flags == ACB_WSTRUST) {
+ /* pull in all the template attributes */
+ ret = samdb_copy_template(d_state->sam_ctx, mem_ctx, &msg,
+ "(&(name=TemplateMemberServer)(objectclass=userTemplate))");
+ if (ret != 0) {
+ DEBUG(1,("Failed to load TemplateMemberServer from samdb\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ container = "Computers";
+
+ } else if (r->in.acct_flags == ACB_SVRTRUST) {
+ /* pull in all the template attributes */
+ ret = samdb_copy_template(d_state->sam_ctx, mem_ctx, &msg,
+ "(&(name=TemplateDomainController)(objectclass=userTemplate))");
+ if (ret != 0) {
+ DEBUG(1,("Failed to load TemplateDomainController from samdb\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ container = "DomainControllers";
+
+ } else if (r->in.acct_flags == ACB_DOMTRUST) {
+ /* pull in all the template attributes */
+ ret = samdb_copy_template(d_state->sam_ctx, mem_ctx, &msg,
+ "(&(name=TemplateTrustingDomain)(objectclass=userTemplate))");
+ if (ret != 0) {
+ DEBUG(1,("Failed to load TemplateTrustingDomain from samdb\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ container = "ForeignDomains"; /* FIXME: Is this correct?*/
+
+ } else {
+ return NT_STATUS_INVALID_PARAMETER;
}
-
+
/* allocate a rid */
status = samdb_allocate_next_id(d_state->sam_ctx, mem_ctx,
d_state->domain_dn, "nextRid", &rid);
@@ -650,7 +692,7 @@ static NTSTATUS samr_CreateUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX
}
/* add core elements to the ldb_message for the user */
- msg.dn = talloc_asprintf(mem_ctx, "CN=%s,CN=Users,%s", username, d_state->domain_dn);
+ msg.dn = talloc_asprintf(mem_ctx, "CN=%s,CN=%s,%s", username, container, d_state->domain_dn);
if (!msg.dn) {
return NT_STATUS_NO_MEMORY;
}
@@ -723,7 +765,7 @@ static NTSTATUS samr_CreateUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *
/* a simple wrapper around samr_CreateUser2 works nicely */
r2.in.handle = r->in.handle;
r2.in.username = r->in.username;
- r2.in.acct_flags = 1234;
+ r2.in.acct_flags = ACB_NORMAL;
r2.in.access_mask = r->in.access_mask;
r2.out.acct_handle = r->out.acct_handle;
r2.out.access_granted = &access_granted;
@@ -914,18 +956,9 @@ static NTSTATUS samr_LookupNames(struct dcesrv_call_state *dce_call, TALLOC_CTX
continue;
}
- switch (atype & 0xF0000000) {
- case ATYPE_ACCOUNT:
- rtype = SID_NAME_USER;
- break;
- case ATYPE_GLOBAL_GROUP:
- rtype = SID_NAME_DOM_GRP;
- break;
- case ATYPE_LOCAL_GROUP:
- rtype = SID_NAME_ALIAS;
- break;
- default:
- DEBUG(1,("Unknown sAMAccountType 0x%08x\n", atype));
+ rtype = samdb_atype_map(atype);
+
+ if (rtype == SID_NAME_UNKNOWN) {
status = STATUS_SOME_UNMAPPED;
continue;
}
@@ -1654,7 +1687,7 @@ static NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call,
so the domain password policy can be used */
return samdb_set_password(a_state->sam_ctx, mem_ctx,
a_state->account_dn, a_state->domain_state->domain_dn,
- msg, new_pass);
+ msg, new_pass, False /* This is a password set, not change */);
}
/*
diff --git a/source4/rpc_server/samr/samdb.c b/source4/rpc_server/samr/samdb.c
index 2fa17af8ea..2489dae684 100644
--- a/source4/rpc_server/samr/samdb.c
+++ b/source4/rpc_server/samr/samdb.c
@@ -416,6 +416,68 @@ uint_t samdb_result_hashes(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
return count;
}
+NTSTATUS samdb_result_passwords(TALLOC_CTX *mem_ctx, struct ldb_message *msg,
+ uint8 **lm_pwd, uint8 **nt_pwd)
+{
+
+ const char *unicodePwd = samdb_result_string(msg, "unicodePwd", NULL);
+
+ struct samr_Hash *lmPwdHash, *ntPwdHash;
+ if (unicodePwd) {
+ if (nt_pwd) {
+ ntPwdHash = talloc_p(mem_ctx, struct samr_Hash);
+ if (!ntPwdHash) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ E_md4hash(unicodePwd, ntPwdHash->hash);
+ *nt_pwd = ntPwdHash->hash;
+ }
+
+ if (lm_pwd) {
+ BOOL lm_hash_ok;
+
+ lmPwdHash = talloc_p(mem_ctx, struct samr_Hash);
+ if (!lmPwdHash) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* compute the new nt and lm hashes */
+ lm_hash_ok = E_deshash(unicodePwd, lmPwdHash->hash);
+
+ if (lm_hash_ok) {
+ *lm_pwd = lmPwdHash->hash;
+ } else {
+ *lm_pwd = NULL;
+ }
+ }
+ } else {
+ if (nt_pwd) {
+ int num_nt;
+ num_nt = samdb_result_hashes(mem_ctx, msg, "ntPwdHash", &ntPwdHash);
+ if (num_nt == 0) {
+ nt_pwd = NULL;
+ } else if (num_nt > 1) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ } else {
+ *nt_pwd = ntPwdHash[0].hash;
+ }
+ }
+ if (lm_pwd) {
+ int num_lm;
+ num_lm = samdb_result_hashes(mem_ctx, msg, "lmPwdHash", &lmPwdHash);
+ if (num_lm == 0) {
+ *lm_pwd = NULL;
+ } else if (num_lm > 1) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ } else {
+ *lm_pwd = lmPwdHash[0].hash;
+ }
+ }
+
+ }
+ return NT_STATUS_OK;
+}
/*
pull a samr_LogonHours structutre from a result set.
@@ -438,36 +500,13 @@ struct samr_LogonHours samdb_result_logon_hours(TALLOC_CTX *mem_ctx, struct ldb_
return hours;
}
-/* mapping between ADS userAccountControl and SAMR acct_flags */
-static const struct {
- uint32 uf, acb;
-} acct_flags_map[] = {
- { UF_ACCOUNTDISABLE, ACB_DISABLED },
- { UF_HOMEDIR_REQUIRED, ACB_HOMDIRREQ },
- { UF_PASSWD_NOTREQD, ACB_PWNOTREQ },
- { UF_TEMP_DUPLICATE_ACCOUNT, ACB_TEMPDUP },
- { UF_NORMAL_ACCOUNT, ACB_NORMAL },
- { UF_MNS_LOGON_ACCOUNT, ACB_MNS },
- { UF_INTERDOMAIN_TRUST_ACCOUNT, ACB_DOMTRUST },
- { UF_WORKSTATION_TRUST_ACCOUNT, ACB_WSTRUST },
- { UF_SERVER_TRUST_ACCOUNT, ACB_SVRTRUST },
- { UF_DONT_EXPIRE_PASSWD, ACB_PWNOEXP },
- { UF_LOCKOUT, ACB_AUTOLOCK }
-};
-
/*
pull a set of account_flags from a result set.
*/
-uint32 samdb_result_acct_flags(struct ldb_message *msg, const char *attr)
+uint16 samdb_result_acct_flags(struct ldb_message *msg, const char *attr)
{
uint_t userAccountControl = ldb_msg_find_uint(msg, attr, 0);
- uint32 i, ret = 0;
- for (i=0;i<ARRAY_SIZE(acct_flags_map);i++) {
- if (acct_flags_map[i].uf & userAccountControl) {
- ret |= acct_flags_map[i].acb;
- }
- }
- return ret;
+ return samdb_uf2acb(userAccountControl);
}
/*
@@ -707,13 +746,7 @@ int samdb_msg_add_hashes(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg
int samdb_msg_add_acct_flags(void *ctx, TALLOC_CTX *mem_ctx, struct ldb_message *msg,
const char *attr_name, uint32 v)
{
- uint_t i, flags = 0;
- for (i=0;i<ARRAY_SIZE(acct_flags_map);i++) {
- if (acct_flags_map[i].acb & v) {
- flags |= acct_flags_map[i].uf;
- }
- }
- return samdb_msg_add_uint(ctx, mem_ctx, msg, attr_name, flags);
+ return samdb_msg_add_uint(ctx, mem_ctx, msg, attr_name, samdb_acb2uf(v));
}
/*
@@ -808,7 +841,8 @@ static BOOL samdb_password_complexity_ok(const char *pass)
*/
NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx,
const char *user_dn, const char *domain_dn,
- struct ldb_message *mod, const char *new_pass)
+ struct ldb_message *mod, const char *new_pass,
+ BOOL user_change)
{
const char * const user_attrs[] = { "userAccountControl", "lmPwdHistory",
"ntPwdHistory", "unicodePwd",
@@ -863,71 +897,73 @@ NTSTATUS samdb_set_password(void *ctx, TALLOC_CTX *mem_ctx,
minPwdLength = samdb_result_uint(res[0], "minPwdLength", 0);
minPwdAge = samdb_result_double(res[0], "minPwdAge", 0);
- /* are all password changes disallowed? */
- if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
-
- /* can this user change password? */
- if (userAccountControl & UF_PASSWD_CANT_CHANGE) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
-
- /* check the various password restrictions */
- if (minPwdLength > str_charnum(new_pass)) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
-
- /* yes, this is a minus. The ages are in negative 100nsec units! */
- if (pwdLastSet - minPwdAge > now_double) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
-
- /* possibly check password complexity */
- if (pwdProperties & DOMAIN_PASSWORD_COMPLEX &&
- !samdb_password_complexity_ok(new_pass)) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
-
/* compute the new nt and lm hashes */
lm_hash_ok = E_deshash(new_pass, lmNewHash.hash);
E_md4hash(new_pass, ntNewHash.hash);
- /* check the immediately past password */
- if (pwdHistoryLength > 0) {
- if (lm_hash_ok && memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0) {
+ if (user_change) {
+ /* are all password changes disallowed? */
+ if (pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) {
return NT_STATUS_PASSWORD_RESTRICTION;
}
- if (memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) {
+
+ /* can this user change password? */
+ if (userAccountControl & UF_PASSWD_CANT_CHANGE) {
return NT_STATUS_PASSWORD_RESTRICTION;
}
- }
-
- /* check the password history */
- lmPwdHistory_len = MIN(lmPwdHistory_len, pwdHistoryLength);
- ntPwdHistory_len = MIN(ntPwdHistory_len, pwdHistoryLength);
-
- if (pwdHistoryLength > 0) {
- if (unicodePwd && strcmp(unicodePwd, new_pass) == 0) {
+
+ /* yes, this is a minus. The ages are in negative 100nsec units! */
+ if (pwdLastSet - minPwdAge > now_double) {
return NT_STATUS_PASSWORD_RESTRICTION;
}
- if (lm_hash_ok && memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0) {
- return NT_STATUS_PASSWORD_RESTRICTION;
+
+ /* check the immediately past password */
+ if (pwdHistoryLength > 0) {
+ if (lm_hash_ok && memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ if (memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
}
- if (memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) {
- return NT_STATUS_PASSWORD_RESTRICTION;
+
+ /* check the password history */
+ lmPwdHistory_len = MIN(lmPwdHistory_len, pwdHistoryLength);
+ ntPwdHistory_len = MIN(ntPwdHistory_len, pwdHistoryLength);
+
+ if (pwdHistoryLength > 0) {
+ if (unicodePwd && strcmp(unicodePwd, new_pass) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ if (lm_hash_ok && memcmp(lmNewHash.hash, lmPwdHash.hash, 16) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ if (memcmp(ntNewHash.hash, ntPwdHash.hash, 16) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+ for (i=0;lm_hash_ok && i<lmPwdHistory_len;i++) {
+ if (memcmp(lmNewHash.hash, lmPwdHistory[i].hash, 16) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+ for (i=0;i<ntPwdHistory_len;i++) {
+ if (memcmp(ntNewHash.hash, ntPwdHistory[i].hash, 16) == 0) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
}
}
- for (i=0;lm_hash_ok && i<lmPwdHistory_len;i++) {
- if (memcmp(lmNewHash.hash, lmPwdHistory[i].hash, 16) == 0) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
+ /* check the various password restrictions */
+ if (minPwdLength > str_charnum(new_pass)) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
}
- for (i=0;i<ntPwdHistory_len;i++) {
- if (memcmp(ntNewHash.hash, ntPwdHistory[i].hash, 16) == 0) {
- return NT_STATUS_PASSWORD_RESTRICTION;
- }
+
+ /* possibly check password complexity */
+ if (pwdProperties & DOMAIN_PASSWORD_COMPLEX &&
+ !samdb_password_complexity_ok(new_pass)) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
}
#define CHECK_RET(x) do { if (x != 0) return NT_STATUS_NO_MEMORY; } while(0)
diff --git a/source4/rpc_server/samr/samr_utils.c b/source4/rpc_server/samr/samr_utils.c
new file mode 100644
index 0000000000..247b2d47f4
--- /dev/null
+++ b/source4/rpc_server/samr/samr_utils.c
@@ -0,0 +1,134 @@
+/*
+ Unix SMB/CIFS implementation.
+ helper mapping functions for the SAMDB server
+
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Andrew Tridgell 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "includes.h"
+
+/*
+translated the ACB_CTRL Flags to UserFlags (userAccountControl)
+*/
+/* mapping between ADS userAccountControl and SAMR acct_flags */
+static const struct {
+ uint32 uf;
+ uint16 acb;
+} acct_flags_map[] = {
+ { UF_ACCOUNTDISABLE, ACB_DISABLED },
+ { UF_HOMEDIR_REQUIRED, ACB_HOMDIRREQ },
+ { UF_PASSWD_NOTREQD, ACB_PWNOTREQ },
+ { UF_TEMP_DUPLICATE_ACCOUNT, ACB_TEMPDUP },
+ { UF_NORMAL_ACCOUNT, ACB_NORMAL },
+ { UF_MNS_LOGON_ACCOUNT, ACB_MNS },
+ { UF_INTERDOMAIN_TRUST_ACCOUNT, ACB_DOMTRUST },
+ { UF_WORKSTATION_TRUST_ACCOUNT, ACB_WSTRUST },
+ { UF_SERVER_TRUST_ACCOUNT, ACB_SVRTRUST },
+ { UF_DONT_EXPIRE_PASSWD, ACB_PWNOEXP },
+ { UF_LOCKOUT, ACB_AUTOLOCK }
+};
+
+uint32 samdb_acb2uf(uint16 acb)
+{
+ uint32 i, ret = 0;
+ for (i=0;i<ARRAY_SIZE(acct_flags_map);i++) {
+ if (acct_flags_map[i].acb & acb) {
+ ret |= acct_flags_map[i].uf;
+ }
+ }
+ return ret;
+}
+
+/*
+translated the UserFlags (userAccountControl) to ACB_CTRL Flags
+*/
+uint16 samdb_uf2acb(uint32 uf)
+{
+ uint32 i;
+ uint16 ret = 0;
+ for (i=0;i<ARRAY_SIZE(acct_flags_map);i++) {
+ if (acct_flags_map[i].uf & uf) {
+ ret |= acct_flags_map[i].acb;
+ }
+ }
+ return ret;
+}
+
+/*
+get the accountType from the UserFlags
+*/
+uint32 samdb_uf2atype(uint32 uf)
+{
+ uint32 atype = 0x00000000;
+
+ if (uf & UF_NORMAL_ACCOUNT) atype = ATYPE_NORMAL_ACCOUNT;
+ else if (uf & UF_TEMP_DUPLICATE_ACCOUNT) atype = ATYPE_NORMAL_ACCOUNT;
+ else if (uf & UF_SERVER_TRUST_ACCOUNT) atype = ATYPE_WORKSTATION_TRUST;
+ else if (uf & UF_WORKSTATION_TRUST_ACCOUNT) atype = ATYPE_WORKSTATION_TRUST;
+ else if (uf & UF_INTERDOMAIN_TRUST_ACCOUNT) atype = ATYPE_INTERDOMAIN_TRUST;
+
+ return atype;
+}
+
+/*
+get the accountType from the groupType
+*/
+uint32 samdb_gtype2atype(uint32 gtype)
+{
+ uint32 atype = 0x00000000;
+
+ switch(gtype) {
+ case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+ atype = ATYPE_SECURITY_LOCAL_GROUP;
+ break;
+ case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
+ atype = ATYPE_SECURITY_LOCAL_GROUP;
+ break;
+ case GTYPE_SECURITY_GLOBAL_GROUP:
+ atype = ATYPE_SECURITY_GLOBAL_GROUP;
+ break;
+
+ case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
+ atype = ATYPE_DISTRIBUTION_GLOBAL_GROUP;
+ break;
+ case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
+ atype = ATYPE_DISTRIBUTION_UNIVERSAL_GROUP;
+ break;
+ case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
+ atype = ATYPE_DISTRIBUTION_LOCAL_GROUP;
+ break;
+ }
+
+ return atype;
+}
+
+/* turn a sAMAccountType into a SID_NAME_USE */
+enum SID_NAME_USE samdb_atype_map(uint32 atype)
+{
+ switch (atype & 0xF0000000) {
+ case ATYPE_GLOBAL_GROUP:
+ return SID_NAME_DOM_GRP;
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ return SID_NAME_ALIAS;
+ case ATYPE_ACCOUNT:
+ return SID_NAME_USER;
+ default:
+ DEBUG(1,("hmm, need to map account type 0x%x\n", atype));
+ }
+ return SID_NAME_UNKNOWN;
+}