[Patches] auth/credentials for user_auth_info

Stefan Metzmacher metze at samba.org
Fri Dec 16 21:58:32 UTC 2016


Hi,

here're some patches to prepare the auth/credentials logic for
usage within struct user_auth_info.

This took quite some cycles to pass a full autobuild, it turns
out that we have a lot of explicit and implicit test cavarage
of the source3 POPT_COMMON_CREDENTIALS handling.

Please review and push:-)

This depends on the "Avoid selftest/autobuild interaction with /tmp"
patchset.

Thanks!
metze
-------------- next part --------------
From e579c1ad506cf9cb308f230974aba6da20381d28 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 14 Dec 2016 08:50:51 +0100
Subject: [PATCH 01/19] auth/credentials: make use of talloc_zero() in
 cli_credentials_init()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c | 76 +-----------------------------------------
 1 file changed, 1 insertion(+), 75 deletions(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index c8f86ba..7f4c15f 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -36,85 +36,11 @@
  */
 _PUBLIC_ struct cli_credentials *cli_credentials_init(TALLOC_CTX *mem_ctx) 
 {
-	struct cli_credentials *cred = talloc(mem_ctx, struct cli_credentials);
+	struct cli_credentials *cred = talloc_zero(mem_ctx, struct cli_credentials);
 	if (cred == NULL) {
 		return cred;
 	}
 
-	cred->workstation_obtained = CRED_UNINITIALISED;
-	cred->username_obtained = CRED_UNINITIALISED;
-	cred->password_obtained = CRED_UNINITIALISED;
-	cred->domain_obtained = CRED_UNINITIALISED;
-	cred->realm_obtained = CRED_UNINITIALISED;
-	cred->ccache_obtained = CRED_UNINITIALISED;
-	cred->client_gss_creds_obtained = CRED_UNINITIALISED;
-	cred->principal_obtained = CRED_UNINITIALISED;
-	cred->keytab_obtained = CRED_UNINITIALISED;
-	cred->server_gss_creds_obtained = CRED_UNINITIALISED;
-
-	cred->ccache_threshold = CRED_UNINITIALISED;
-	cred->client_gss_creds_threshold = CRED_UNINITIALISED;
-
-	cred->workstation = NULL;
-	cred->username = NULL;
-	cred->password = NULL;
-	cred->old_password = NULL;
-	cred->domain = NULL;
-	cred->realm = NULL;
-	cred->principal = NULL;
-	cred->salt_principal = NULL;
-	cred->impersonate_principal = NULL;
-	cred->self_service = NULL;
-	cred->target_service = NULL;
-
-	cred->bind_dn = NULL;
-
-	cred->nt_hash = NULL;
-	cred->old_nt_hash = NULL;
-
-	cred->lm_response.data = NULL;
-	cred->lm_response.length = 0;
-	cred->nt_response.data = NULL;
-	cred->nt_response.length = 0;
-
-	cred->ccache = NULL;
-	cred->client_gss_creds = NULL;
-	cred->keytab = NULL;
-	cred->server_gss_creds = NULL;
-
-	cred->workstation_cb = NULL;
-	cred->password_cb = NULL;
-	cred->username_cb = NULL;
-	cred->domain_cb = NULL;
-	cred->realm_cb = NULL;
-	cred->principal_cb = NULL;
-
-	cred->priv_data = NULL;
-
-	cred->netlogon_creds = NULL;
-	cred->secure_channel_type = SEC_CHAN_NULL;
-
-	cred->kvno = 0;
-
-	cred->password_last_changed_time = 0;
-
-	cred->smb_krb5_context = NULL;
-
-	cred->machine_account_pending = false;
-	cred->machine_account_pending_lp_ctx = NULL;
-
-	cred->machine_account = false;
-
-	cred->password_tries = 0;
-
-	cred->callback_running = false;
-
-	cli_credentials_set_kerberos_state(cred, CRED_AUTO_USE_KERBEROS);
-	cli_credentials_set_gensec_features(cred, 0);
-	cli_credentials_set_krb_forwardable(cred, CRED_AUTO_KRB_FORWARDABLE);
-
-	cred->forced_sasl_mech = NULL;
-
 	cred->winbind_separator = '\\';
 
 	return cred;
-- 
1.9.1


From a6277b007bc75f9a3402d934bfd753343062b3ae Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 14 Dec 2016 08:52:12 +0100
Subject: [PATCH 02/19] auth/credentials: let cli_credentials_set_password()
 fail if talloc_strdup() fails

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index 7f4c15f..17f4b5d 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -339,18 +339,31 @@ _PUBLIC_ bool cli_credentials_set_password(struct cli_credentials *cred,
 				  enum credentials_obtained obtained)
 {
 	if (obtained >= cred->password_obtained) {
+
+		cred->lm_response = data_blob_null;
+		cred->nt_response = data_blob_null;
+		cred->nt_hash = NULL;
+		cred->password = NULL;
+
+		cli_credentials_invalidate_ccache(cred, obtained);
+
 		cred->password_tries = 0;
+
+		if (val == NULL) {
+			cred->password_obtained = obtained;
+			return true;
+		}
+
 		cred->password = talloc_strdup(cred, val);
-		if (cred->password) {
-			/* Don't print the actual password in talloc memory dumps */
-			talloc_set_name_const(cred->password, "password set via cli_credentials_set_password");
+		if (cred->password == NULL) {
+			return false;
 		}
+
+		/* Don't print the actual password in talloc memory dumps */
+		talloc_set_name_const(cred->password,
+			"password set via cli_credentials_set_password");
 		cred->password_obtained = obtained;
-		cli_credentials_invalidate_ccache(cred, cred->password_obtained);
 
-		cred->nt_hash = NULL;
-		cred->lm_response = data_blob(NULL, 0);
-		cred->nt_response = data_blob(NULL, 0);
 		return true;
 	}
 
-- 
1.9.1


From 5cb3c481ba026873e2893133ca603ff0003cf930 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 14 Dec 2016 10:02:10 +0100
Subject: [PATCH 03/19] auth/credentials: add
 cli_credentials_set_password_will_be_nt_hash() and the related logic

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c          | 107 +++++++++++++++++++++++++++-----
 auth/credentials/credentials.h          |   2 +
 auth/credentials/credentials_internal.h |   2 +
 auth/credentials/credentials_ntlm.c     |  19 ++++++
 auth/credentials/pycredentials.c        |  20 ++++++
 5 files changed, 135 insertions(+), 15 deletions(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index 17f4b5d..e024428 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -318,7 +318,8 @@ _PUBLIC_ const char *cli_credentials_get_password(struct cli_credentials *cred)
 	}
 
 	if (cred->password_obtained == CRED_CALLBACK && 
-	    !cred->callback_running) {
+	    !cred->callback_running &&
+	    !cred->password_will_be_nt_hash) {
 		cred->callback_running = true;
 		cred->password = cred->password_cb(cred);
 		cred->callback_running = false;
@@ -354,6 +355,29 @@ _PUBLIC_ bool cli_credentials_set_password(struct cli_credentials *cred,
 			return true;
 		}
 
+		if (cred->password_will_be_nt_hash) {
+			struct samr_Password *nt_hash = NULL;
+			size_t val_len = strlen(val);
+			size_t converted;
+
+			nt_hash = talloc(cred, struct samr_Password);
+			if (nt_hash == NULL) {
+				return false;
+			}
+
+			converted = strhex_to_str((char *)nt_hash->hash,
+						  sizeof(nt_hash->hash),
+						  val, val_len);
+			if (converted != sizeof(nt_hash->hash)) {
+				TALLOC_FREE(nt_hash);
+				return false;
+			}
+
+			cred->nt_hash = nt_hash;
+			cred->password_obtained = obtained;
+			return true;
+		}
+
 		cred->password = talloc_strdup(cred, val);
 		if (cred->password == NULL) {
 			return false;
@@ -424,32 +448,85 @@ _PUBLIC_ bool cli_credentials_set_old_password(struct cli_credentials *cred,
 _PUBLIC_ struct samr_Password *cli_credentials_get_nt_hash(struct cli_credentials *cred,
 							   TALLOC_CTX *mem_ctx)
 {
+	enum credentials_obtained password_obtained;
+	enum credentials_obtained ccache_threshold;
+	enum credentials_obtained client_gss_creds_threshold;
+	bool password_is_nt_hash;
 	const char *password = NULL;
+	struct samr_Password *nt_hash = NULL;
 
 	if (cred->nt_hash != NULL) {
-		struct samr_Password *nt_hash = talloc(mem_ctx, struct samr_Password);
-		if (!nt_hash) {
-			return NULL;
-		}
+		/*
+		 * If we already have a hash it's easy.
+		 */
+		goto return_hash;
+	}
 
-		*nt_hash = *cred->nt_hash;
+	/*
+	 * This is a bit tricky, with password_will_be_nt_hash
+	 * we still need to get the value via the password_callback
+	 * but if we did that we should not remember it's state
+	 * in the long run so we need to undo it.
+	 */
 
-		return nt_hash;
-	}
+	password_obtained = cred->password_obtained;
+	ccache_threshold = cred->ccache_threshold;
+	client_gss_creds_threshold = cred->client_gss_creds_threshold;
+	password_is_nt_hash = cred->password_will_be_nt_hash;
 
+	cred->password_will_be_nt_hash = false;
 	password = cli_credentials_get_password(cred);
-	if (password) {
-		struct samr_Password *nt_hash = talloc(mem_ctx, struct samr_Password);
-		if (!nt_hash) {
-			return NULL;
-		}
 
+	cred->password_will_be_nt_hash = password_is_nt_hash;
+	if (password_is_nt_hash && password_obtained == CRED_CALLBACK) {
+		/*
+		 * We got the nt_hash as string via the callback,
+		 * so we need to undo the state change.
+		 *
+		 * And also don't remember it as plaintext password.
+		 */
+		cred->client_gss_creds_threshold = client_gss_creds_threshold;
+		cred->ccache_threshold = ccache_threshold;
+		cred->password_obtained = password_obtained;
+		cred->password = NULL;
+	}
+
+	if (password == NULL) {
+		return NULL;
+	}
+
+	nt_hash = talloc(cred, struct samr_Password);
+	if (nt_hash == NULL) {
+		return NULL;
+	}
+
+	if (password_is_nt_hash) {
+		size_t password_len = strlen(password);
+		size_t converted;
+
+		converted = strhex_to_str((char *)nt_hash->hash,
+					  sizeof(nt_hash->hash),
+					  password, password_len);
+		if (converted != sizeof(nt_hash->hash)) {
+			TALLOC_FREE(nt_hash);
+			return false;
+		}
+	} else {
 		E_md4hash(password, nt_hash->hash);
+	}
 
-		return nt_hash;
+	cred->nt_hash = nt_hash;
+	nt_hash = NULL;
+
+return_hash:
+	nt_hash = talloc(mem_ctx, struct samr_Password);
+	if (nt_hash == NULL) {
+		return NULL;
 	}
 
-	return NULL;
+	*nt_hash = *cred->nt_hash;
+
+	return nt_hash;
 }
 
 /**
diff --git a/auth/credentials/credentials.h b/auth/credentials/credentials.h
index 523793f..6b0d83b 100644
--- a/auth/credentials/credentials.h
+++ b/auth/credentials/credentials.h
@@ -201,6 +201,8 @@ bool cli_credentials_set_utf16_password(struct cli_credentials *cred,
 					enum credentials_obtained obtained);
 bool cli_credentials_set_old_utf16_password(struct cli_credentials *cred,
 					    const DATA_BLOB *password_utf16);
+void cli_credentials_set_password_will_be_nt_hash(struct cli_credentials *cred,
+						  bool val);
 bool cli_credentials_set_nt_hash(struct cli_credentials *cred,
 				 const struct samr_Password *nt_hash, 
 				 enum credentials_obtained obtained);
diff --git a/auth/credentials/credentials_internal.h b/auth/credentials/credentials_internal.h
index f88ae70..68f1f25 100644
--- a/auth/credentials/credentials_internal.h
+++ b/auth/credentials/credentials_internal.h
@@ -115,6 +115,8 @@ struct cli_credentials {
 	bool callback_running;
 
 	char winbind_separator;
+
+	bool password_will_be_nt_hash;
 };
 
 #endif /* __CREDENTIALS_INTERNAL_H__ */
diff --git a/auth/credentials/credentials_ntlm.c b/auth/credentials/credentials_ntlm.c
index 2a4c141..e6859bf 100644
--- a/auth/credentials/credentials_ntlm.c
+++ b/auth/credentials/credentials_ntlm.c
@@ -301,6 +301,8 @@ _PUBLIC_ bool cli_credentials_set_utf16_password(struct cli_credentials *cred,
 						 const DATA_BLOB *password_utf16,
 						 enum credentials_obtained obtained)
 {
+	cred->password_will_be_nt_hash = false;
+
 	if (password_utf16 == NULL) {
 		return cli_credentials_set_password(cred, NULL, obtained);
 	}
@@ -389,10 +391,27 @@ _PUBLIC_ bool cli_credentials_set_old_utf16_password(struct cli_credentials *cre
 	return true;
 }
 
+_PUBLIC_ void cli_credentials_set_password_will_be_nt_hash(struct cli_credentials *cred,
+							   bool val)
+{
+	/*
+	 * We set this here and the next cli_credentials_set_password()
+	 * that resets the password or password callback
+	 * will pick this up.
+	 *
+	 * cli_credentials_set_nt_hash() and
+	 * cli_credentials_set_utf16_password() will reset this
+	 * to false.
+	 */
+	cred->password_will_be_nt_hash = val;
+}
+
 _PUBLIC_ bool cli_credentials_set_nt_hash(struct cli_credentials *cred,
 				 const struct samr_Password *nt_hash, 
 				 enum credentials_obtained obtained)
 {
+	cred->password_will_be_nt_hash = false;
+
 	if (obtained >= cred->password_obtained) {
 		cli_credentials_set_password(cred, NULL, obtained);
 		if (nt_hash) {
diff --git a/auth/credentials/pycredentials.c b/auth/credentials/pycredentials.c
index 9ea0682..e0b7392 100644
--- a/auth/credentials/pycredentials.c
+++ b/auth/credentials/pycredentials.c
@@ -299,6 +299,21 @@ static PyObject *py_creds_parse_string(PyObject *self, PyObject *args)
 	Py_RETURN_NONE;
 }
 
+static PyObject *py_cli_credentials_set_password_will_be_nt_hash(PyObject *self, PyObject *args)
+{
+	struct cli_credentials *creds = PyCredentials_AsCliCredentials(self);
+	PyObject *py_val = NULL;
+	bool val = false;
+
+	if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &py_val)) {
+		return NULL;
+	}
+	val = PyObject_IsTrue(py_val);
+
+	cli_credentials_set_password_will_be_nt_hash(creds, val);
+	Py_RETURN_NONE;
+}
+
 static PyObject *py_creds_get_nt_hash(PyObject *self, PyObject *unused)
 {
 	PyObject *ret;
@@ -564,6 +579,11 @@ static PyMethodDef py_creds_methods[] = {
 	{ "parse_string", py_creds_parse_string, METH_VARARGS,
 		"S.parse_string(text, obtained=CRED_SPECIFIED) -> None\n"
 		"Parse credentials string." },
+	{ "set_password_will_be_nt_hash",
+		py_cli_credentials_set_password_will_be_nt_hash, METH_VARARGS,
+		"S.set_password_will_be_nt_hash(bool) -> None\n"
+		"Alters the behaviour of S.set_password() "
+		"to expect the NTHASH as hexstring." },
 	{ "get_nt_hash", py_creds_get_nt_hash, METH_NOARGS,
 		NULL },
 	{ "get_kerberos_state", py_creds_get_kerberos_state, METH_NOARGS,
-- 
1.9.1


From 8deffe86ac28dfc28704275e4bbcb14220fbc135 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 09:34:45 +0100
Subject: [PATCH 04/19] tests/credentials.py: add test for
 cli_credentials_set_password_will_be_nt_hash()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index a228327..c27edd77 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -24,6 +24,7 @@ the functionality, that's already done in other tests.
 from samba import credentials
 import samba.tests
 import os
+import binascii
 
 class CredentialsTests(samba.tests.TestCase):
 
@@ -95,8 +96,19 @@ class CredentialsTests(samba.tests.TestCase):
         self.assertEqual("myworksta", self.creds.get_workstation())
 
     def test_get_nt_hash(self):
-        self.creds.set_password("geheim")
-        self.assertEqual('\xc2\xae\x1f\xe6\xe6H\x84cRE>\x81o*\xeb\x93',
+        password="geheim"
+        hex_nthash="c2ae1fe6e648846352453e816f2aeb93"
+        self.creds.set_password(password)
+        self.assertEqual(password, self.creds.get_password())
+        self.assertEqual(binascii.a2b_hex(hex_nthash),
+                         self.creds.get_nt_hash())
+
+    def test_get_nt_hash_string(self):
+        self.creds.set_password_will_be_nt_hash(True)
+        hex_nthash="c2ae1fe6e648846352453e816f2aeb93"
+        self.creds.set_password(hex_nthash)
+        self.assertEqual(None, self.creds.get_password())
+        self.assertEqual(binascii.a2b_hex(hex_nthash),
                          self.creds.get_nt_hash())
 
     def test_set_cmdline_callbacks(self):
-- 
1.9.1


From d18e69a06a5966f2aae0ced4403d39abbbe4d443 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 09:42:20 +0100
Subject: [PATCH 05/19] tests/credentials.py: verify the difference of
 parse_string("someone") and parse_string("someone%")

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index c27edd77..ad3f663 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -70,6 +70,16 @@ class CredentialsTests(samba.tests.TestCase):
         self.assertEqual("", self.creds.get_username())
         self.assertEqual(None, self.creds.get_password())
 
+    def test_parse_string_empty_pw(self):
+        self.creds.parse_string("someone%")
+        self.assertEqual("someone", self.creds.get_username())
+        self.assertEqual("", self.creds.get_password())
+
+    def test_parse_string_none_pw(self):
+        self.creds.parse_string("someone")
+        self.assertEqual("someone", self.creds.get_username())
+        self.assertEqual(None, self.creds.get_password())
+
     def test_parse_string_user_pw_domain(self):
         self.creds.parse_string("dom\\someone%secr")
         self.assertEqual("someone", self.creds.get_username())
-- 
1.9.1


From 605e3a23a8261cf13f9f1caffc0a85ae0c8eb0cb Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 10:06:25 +0100
Subject: [PATCH 06/19] auth/credentials: add py_creds_parse_file()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/pycredentials.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/auth/credentials/pycredentials.c b/auth/credentials/pycredentials.c
index e0b7392..d38d0e3 100644
--- a/auth/credentials/pycredentials.c
+++ b/auth/credentials/pycredentials.c
@@ -299,6 +299,21 @@ static PyObject *py_creds_parse_string(PyObject *self, PyObject *args)
 	Py_RETURN_NONE;
 }
 
+static PyObject *py_creds_parse_file(PyObject *self, PyObject *args)
+{
+	char *newval;
+	enum credentials_obtained obt = CRED_SPECIFIED;
+	int _obt = obt;
+
+	if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) {
+		return NULL;
+	}
+	obt = _obt;
+
+	cli_credentials_parse_file(PyCredentials_AsCliCredentials(self), newval, obt);
+	Py_RETURN_NONE;
+}
+
 static PyObject *py_cli_credentials_set_password_will_be_nt_hash(PyObject *self, PyObject *args)
 {
 	struct cli_credentials *creds = PyCredentials_AsCliCredentials(self);
@@ -579,6 +594,9 @@ static PyMethodDef py_creds_methods[] = {
 	{ "parse_string", py_creds_parse_string, METH_VARARGS,
 		"S.parse_string(text, obtained=CRED_SPECIFIED) -> None\n"
 		"Parse credentials string." },
+	{ "parse_file", py_creds_parse_file, METH_VARARGS,
+		"S.parse_file(filename, obtained=CRED_SPECIFIED) -> None\n"
+		"Parse credentials file." },
 	{ "set_password_will_be_nt_hash",
 		py_cli_credentials_set_password_will_be_nt_hash, METH_VARARGS,
 		"S.set_password_will_be_nt_hash(bool) -> None\n"
-- 
1.9.1


From 4b268fadf90275104950e2d60aea9c18e8f0c297 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 10:30:29 +0100
Subject: [PATCH 07/19] tests/credentials.py: add very simple test for
 py_creds_parse_file

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index ad3f663..0a64179d 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -26,7 +26,7 @@ import samba.tests
 import os
 import binascii
 
-class CredentialsTests(samba.tests.TestCase):
+class CredentialsTests(samba.tests.TestCaseInTempDir):
 
     def setUp(self):
         super(CredentialsTests, self).setUp()
@@ -156,6 +156,28 @@ class CredentialsTests(samba.tests.TestCase):
         self.assertEqual(creds.is_anonymous(), True)
         self.assertEqual(creds.authentication_requested(), False)
 
+    def test_parse_file_1(self):
+        realm="realm.example.com"
+        domain="dom"
+        password="pass"
+        username="user"
+
+        passwd_file_name = os.path.join(self.tempdir, "parse_file")
+        passwd_file_fd = open(passwd_file_name, 'wx')
+        passwd_file_fd.write("realm=%s\n" % realm)
+        passwd_file_fd.write("domain=%s\n" % domain)
+        passwd_file_fd.write("username=%s\n" % username)
+        passwd_file_fd.write("password=%s\n" % password)
+        passwd_file_fd.close()
+        self.creds.parse_file(passwd_file_name)
+        self.assertEqual(self.creds.get_username(), username)
+        self.assertEqual(self.creds.get_password(), password)
+        self.assertEqual(self.creds.get_domain(), domain.upper())
+        self.assertEqual(self.creds.get_realm(), realm.upper())
+        self.assertEqual(self.creds.is_anonymous(), False)
+        self.assertEqual(self.creds.authentication_requested(), True)
+        os.unlink(passwd_file_name)
+
     def test_parse_username(self):
         creds = credentials.Credentials()
         lp = samba.tests.env_loadparm()
-- 
1.9.1


From 2fb70a73e7098f807b79ebe002780b46c685defd Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 11:37:33 +0100
Subject: [PATCH 08/19] auth/credentials: add python bindings for enum
 credentials_obtained

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/pycredentials.c | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/auth/credentials/pycredentials.c b/auth/credentials/pycredentials.c
index d38d0e3..6da64a6 100644
--- a/auth/credentials/pycredentials.c
+++ b/auth/credentials/pycredentials.c
@@ -528,12 +528,12 @@ static PyMethodDef py_creds_methods[] = {
 	{ "get_username", py_creds_get_username, METH_NOARGS,
 		"S.get_username() -> username\nObtain username." },
 	{ "set_username", py_creds_set_username, METH_VARARGS,
-		"S.set_username(name, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_username(name[, credentials.SPECIFIED]) -> None\n"
 		"Change username." },
 	{ "get_principal", py_creds_get_principal, METH_NOARGS,
 		"S.get_principal() -> user at realm\nObtain user principal." },
 	{ "set_principal", py_creds_set_principal, METH_VARARGS,
-		"S.set_principal(name, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_principal(name[, credentials.SPECIFIED]) -> None\n"
 		"Change principal." },
 	{ "get_password", py_creds_get_password, METH_NOARGS,
 		"S.get_password() -> password\n"
@@ -542,31 +542,31 @@ static PyMethodDef py_creds_methods[] = {
 		"S.get_ntlm_username_domain() -> (domain, username)\n"
 		"Obtain NTLM username and domain, split up either as (DOMAIN, user) or (\"\", \"user at realm\")." },
 	{ "set_password", py_creds_set_password, METH_VARARGS,
-		"S.set_password(password, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_password(password[, credentials.SPECIFIED]) -> None\n"
 		"Change password." },
 	{ "set_utf16_password", py_creds_set_utf16_password, METH_VARARGS,
-		"S.set_utf16_password(password, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_utf16_password(password[, credentials.SPECIFIED]) -> None\n"
 		"Change password." },
 	{ "get_old_password", py_creds_get_old_password, METH_NOARGS,
 		"S.get_old_password() -> password\n"
 		"Obtain old password." },
 	{ "set_old_password", py_creds_set_old_password, METH_VARARGS,
-		"S.set_old_password(password, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_old_password(password[, credentials.SPECIFIED]) -> None\n"
 		"Change old password." },
 	{ "set_old_utf16_password", py_creds_set_old_utf16_password, METH_VARARGS,
-		"S.set_old_utf16_password(password, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_old_utf16_password(password[, credentials.SPECIFIED]) -> None\n"
 		"Change old password." },
 	{ "get_domain", py_creds_get_domain, METH_NOARGS,
 		"S.get_domain() -> domain\n"
 		"Obtain domain name." },
 	{ "set_domain", py_creds_set_domain, METH_VARARGS,
-		"S.set_domain(domain, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_domain(domain[, credentials.SPECIFIED]) -> None\n"
 		"Change domain name." },
 	{ "get_realm", py_creds_get_realm, METH_NOARGS,
 		"S.get_realm() -> realm\n"
 		"Obtain realm name." },
 	{ "set_realm", py_creds_set_realm, METH_VARARGS,
-		"S.set_realm(realm, obtained=CRED_SPECIFIED) -> None\n"
+		"S.set_realm(realm[, credentials.SPECIFIED]) -> None\n"
 		"Change realm name." },
 	{ "get_bind_dn", py_creds_get_bind_dn, METH_NOARGS,
 		"S.get_bind_dn() -> bind dn\n"
@@ -592,10 +592,10 @@ static PyMethodDef py_creds_methods[] = {
 		"S.set_cmdline_callbacks() -> bool\n"
 		"Use command-line to obtain credentials not explicitly set." },
 	{ "parse_string", py_creds_parse_string, METH_VARARGS,
-		"S.parse_string(text, obtained=CRED_SPECIFIED) -> None\n"
+		"S.parse_string(text[, credentials.SPECIFIED]) -> None\n"
 		"Parse credentials string." },
 	{ "parse_file", py_creds_parse_file, METH_VARARGS,
-		"S.parse_file(filename, obtained=CRED_SPECIFIED) -> None\n"
+		"S.parse_file(filename[, credentials.SPECIFIED]) -> None\n"
 		"Parse credentials file." },
 	{ "set_password_will_be_nt_hash",
 		py_cli_credentials_set_password_will_be_nt_hash, METH_VARARGS,
@@ -649,6 +649,13 @@ void initcredentials(void)
 	if (m == NULL)
 		return;
 
+	PyModule_AddObject(m, "UNINITIALISED", PyInt_FromLong(CRED_UNINITIALISED));
+	PyModule_AddObject(m, "CALLBACK", PyInt_FromLong(CRED_CALLBACK));
+	PyModule_AddObject(m, "GUESS_ENV", PyInt_FromLong(CRED_GUESS_ENV));
+	PyModule_AddObject(m, "GUESS_FILE", PyInt_FromLong(CRED_GUESS_FILE));
+	PyModule_AddObject(m, "CALLBACK_RESULT", PyInt_FromLong(CRED_CALLBACK_RESULT));
+	PyModule_AddObject(m, "SPECIFIED", PyInt_FromLong(CRED_SPECIFIED));
+
 	PyModule_AddObject(m, "AUTO_USE_KERBEROS", PyInt_FromLong(CRED_AUTO_USE_KERBEROS));
 	PyModule_AddObject(m, "DONT_USE_KERBEROS", PyInt_FromLong(CRED_DONT_USE_KERBEROS));
 	PyModule_AddObject(m, "MUST_USE_KERBEROS", PyInt_FromLong(CRED_MUST_USE_KERBEROS));
-- 
1.9.1


From f6abd98d87c9df95c1eab1fb5ad91f1182157c2d Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 11:04:02 +0100
Subject: [PATCH 09/19] auth/credentials: handle situations without a
 configured (default) realm

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c    | 43 +++++++++++++++++++++++++++++++--------
 python/samba/tests/credentials.py | 19 ++++++++++++-----
 2 files changed, 48 insertions(+), 14 deletions(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index e024428..a0f91e9 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -213,16 +213,37 @@ _PUBLIC_ const char *cli_credentials_get_principal_and_obtained(struct cli_crede
 
 	if (cred->principal_obtained < cred->username_obtained
 	    || cred->principal_obtained < MAX(cred->domain_obtained, cred->realm_obtained)) {
+		const char *effective_username = NULL;
+		const char *effective_realm = NULL;
+		enum credentials_obtained effective_obtained;
+
+		effective_username = cli_credentials_get_username(cred);
+		if (effective_username == NULL || strlen(effective_username) == 0) {
+			*obtained = cred->username_obtained;
+			return NULL;
+		}
+
 		if (cred->domain_obtained > cred->realm_obtained) {
-			*obtained = MIN(cred->domain_obtained, cred->username_obtained);
-			return talloc_asprintf(mem_ctx, "%s@%s", 
-					       cli_credentials_get_username(cred),
-					       cli_credentials_get_domain(cred));
+			effective_realm = cli_credentials_get_domain(cred);
+			effective_obtained = MIN(cred->domain_obtained,
+						 cred->username_obtained);
 		} else {
-			*obtained = MIN(cred->realm_obtained, cred->username_obtained);
+			effective_realm = cli_credentials_get_realm(cred);
+			effective_obtained = MIN(cred->realm_obtained,
+						 cred->username_obtained);
+		}
+
+		if (effective_realm == NULL || strlen(effective_realm) == 0) {
+			effective_realm = cli_credentials_get_domain(cred);
+			effective_obtained = MIN(cred->domain_obtained,
+						 cred->username_obtained);
+		}
+
+		if (effective_realm != NULL && strlen(effective_realm) != 0) {
+			*obtained = effective_obtained;
 			return talloc_asprintf(mem_ctx, "%s@%s", 
-					       cli_credentials_get_username(cred),
-					       cli_credentials_get_realm(cred));
+					       effective_username,
+					       effective_realm);
 		}
 	}
 	*obtained = cred->principal_obtained;
@@ -816,6 +837,7 @@ _PUBLIC_ void cli_credentials_set_conf(struct cli_credentials *cred,
 			      struct loadparm_context *lp_ctx)
 {
 	const char *sep = NULL;
+	const char *realm = lpcfg_realm(lp_ctx);
 
 	cli_credentials_set_username(cred, "", CRED_UNINITIALISED);
 	if (lpcfg_parm_is_cmdline(lp_ctx, "workgroup")) {
@@ -828,10 +850,13 @@ _PUBLIC_ void cli_credentials_set_conf(struct cli_credentials *cred,
 	} else {
 		cli_credentials_set_workstation(cred, lpcfg_netbios_name(lp_ctx), CRED_UNINITIALISED);
 	}
+	if (realm != NULL && strlen(realm) == 0) {
+		realm = NULL;
+	}
 	if (lpcfg_parm_is_cmdline(lp_ctx, "realm")) {
-		cli_credentials_set_realm(cred, lpcfg_realm(lp_ctx), CRED_SPECIFIED);
+		cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
 	} else {
-		cli_credentials_set_realm(cred, lpcfg_realm(lp_ctx), CRED_UNINITIALISED);
+		cli_credentials_set_realm(cred, realm, CRED_UNINITIALISED);
 	}
 
 	sep = lpcfg_winbind_separator(lp_ctx);
diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index 0a64179d..1cbd540 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -60,10 +60,12 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
     def test_set_domain(self):
         self.creds.set_domain("ABMAS")
         self.assertEqual("ABMAS", self.creds.get_domain())
+        self.assertEqual(self.creds.get_principal(), None)
 
     def test_set_realm(self):
         self.creds.set_realm("myrealm")
         self.assertEqual("MYREALM", self.creds.get_realm())
+        self.assertEqual(self.creds.get_principal(), None)
 
     def test_parse_string_anon(self):
         self.creds.parse_string("%")
@@ -140,7 +142,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         creds.guess(lp)
         self.assertEqual(creds.get_username(), "env_user")
         self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
-        self.assertEqual(creds.get_realm(), lp.get("realm").upper())
+        self.assertEqual(creds.get_realm(), None)
+        self.assertEqual(creds.get_principal(), "env_user@%s" % creds.get_domain())
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), False)
 
@@ -153,6 +156,7 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.get_username(), "")
         self.assertEqual(creds.get_domain(), "")
         self.assertEqual(creds.get_realm(), None)
+        self.assertEqual(creds.get_principal(), None)
         self.assertEqual(creds.is_anonymous(), True)
         self.assertEqual(creds.authentication_requested(), False)
 
@@ -186,7 +190,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         creds.parse_string("user")
         self.assertEqual(creds.get_username(), "user")
         self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
-        self.assertEqual(creds.get_realm(), lp.get("realm").upper())
+        self.assertEqual(creds.get_realm(), None)
+        self.assertEqual(creds.get_principal(), "user@%s" % lp.get("workgroup").upper())
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
@@ -198,7 +203,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         creds.parse_string("domain\user")
         self.assertEqual(creds.get_username(), "user")
         self.assertEqual(creds.get_domain(), "DOMAIN")
-        self.assertEqual(creds.get_realm(), lp.get("realm").upper())
+        self.assertEqual(creds.get_realm(), None)
+        self.assertEqual(creds.get_principal(), "user at DOMAIN")
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
@@ -211,6 +217,7 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.get_username(), "env_user")
         self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
         self.assertEqual(creds.get_realm(), "SAMBA.ORG")
+        self.assertEqual(creds.get_principal(), "user at samba.org")
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
@@ -223,7 +230,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.get_username(), "user")
         self.assertEqual(creds.get_password(), "pass")
         self.assertEqual(creds.get_domain(), lp.get("workgroup"))
-        self.assertEqual(creds.get_realm(), lp.get("realm"))
+        self.assertEqual(creds.get_realm(), None)
+        self.assertEqual(creds.get_principal(), "user@%s" % lp.get("workgroup"))
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
@@ -236,7 +244,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.get_username(), "user")
         self.assertEqual(creds.get_domain(), "DOMAIN")
         self.assertEqual(creds.get_password(), "pass")
-        self.assertEqual(creds.get_realm(), lp.get("realm"))
+        self.assertEqual(creds.get_realm(), None)
+        self.assertEqual(creds.get_principal(), "user at DOMAIN")
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
-- 
1.9.1


From 297f8e1e363c7ae8496fa7014fd841371f4c5efc Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 14:49:18 +0100
Subject: [PATCH 10/19] tests/credentials.py: add tests with a realm from
 smb.conf

As we don't want to create a new smb.conf file
we just simulate it with "creds.set_realm(realm, credentials.UNINITIALISED)".

That's basically the same as the cli_credentials_set_conf() behaviour
if a realm is specified in the configuration.

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index 1cbd540..8bfab7a 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -182,7 +182,7 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(self.creds.authentication_requested(), True)
         os.unlink(passwd_file_name)
 
-    def test_parse_username(self):
+    def test_parse_username_0(self):
         creds = credentials.Credentials()
         lp = samba.tests.env_loadparm()
         os.environ["USER"] = "env_user"
@@ -195,6 +195,21 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
+    def test_parse_username_1(self):
+        creds = credentials.Credentials()
+        lp = samba.tests.env_loadparm()
+        os.environ["USER"] = "env_user"
+        creds.guess(lp)
+        realm = "realm.example.com"
+        creds.set_realm(realm, credentials.UNINITIALISED)
+        creds.parse_string("user")
+        self.assertEqual(creds.get_username(), "user")
+        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_realm(), realm.upper())
+        self.assertEqual(creds.get_principal(), "user@%s" % realm.upper())
+        self.assertEqual(creds.is_anonymous(), False)
+        self.assertEqual(creds.authentication_requested(), True)
+
     def test_parse_username_with_domain(self):
         creds = credentials.Credentials()
         lp = samba.tests.env_loadparm()
-- 
1.9.1


From d6fd84cc91b85e1eb34e1a7cb0183d18b93223dd Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Fri, 9 Dec 2016 12:20:19 +0100
Subject: [PATCH 11/19] auth/credentials: let cli_credentials_parse_string()
 always reset username and domain

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c    | 8 ++++++++
 python/samba/tests/credentials.py | 8 ++++----
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index a0f91e9..cca772d 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -781,6 +781,14 @@ _PUBLIC_ void cli_credentials_parse_string(struct cli_credentials *credentials,
 	}
 
 	if ((p = strchr_m(uname,'@'))) {
+		/*
+		 * We also need to set username and domain
+		 * in order to undo the effect of
+		 * cli_credentials_guess().
+		 */
+		cli_credentials_set_username(credentials, uname, obtained);
+		cli_credentials_set_domain(credentials, "", obtained);
+
 		cli_credentials_set_principal(credentials, uname, obtained);
 		*p = 0;
 		cli_credentials_set_realm(credentials, p+1, obtained);
diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index 8bfab7a..82a8ad9 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -229,8 +229,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         os.environ["USER"] = "env_user"
         creds.guess(lp)
         creds.parse_string("user at samba.org")
-        self.assertEqual(creds.get_username(), "env_user")
-        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_username(), "user at samba.org")
+        self.assertEqual(creds.get_domain(), "")
         self.assertEqual(creds.get_realm(), "SAMBA.ORG")
         self.assertEqual(creds.get_principal(), "user at samba.org")
         self.assertEqual(creds.is_anonymous(), False)
@@ -270,8 +270,8 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         os.environ["USER"] = "env_user"
         creds.guess(lp)
         creds.parse_string("user at samba.org%pass")
-        self.assertEqual(creds.get_username(), "env_user")
-        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_username(), "user at samba.org")
+        self.assertEqual(creds.get_domain(), "")
         self.assertEqual(creds.get_password(), "pass")
         self.assertEqual(creds.get_realm(), "SAMBA.ORG")
         self.assertEqual(creds.get_principal(), "user at samba.org")
-- 
1.9.1


From c1db2759bb70966bbccaaa031d573f8519fc9737 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Wed, 14 Dec 2016 16:47:57 +0100
Subject: [PATCH 12/19] auth/credentials: let cli_credentials_parse_string()
 always reset principal and realm

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index cca772d..14c8403 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -797,9 +797,40 @@ _PUBLIC_ void cli_credentials_parse_string(struct cli_credentials *credentials,
 		   || (p = strchr_m(uname, '/'))
 		   || (p = strchr_m(uname, credentials->winbind_separator)))
 	{
+		const char *domain = NULL;
+
+		domain = uname;
 		*p = 0;
-		cli_credentials_set_domain(credentials, uname, obtained);
 		uname = p+1;
+
+		if (obtained == credentials->realm_obtained &&
+		    !strequal_m(credentials->domain, domain))
+		{
+			/*
+			 * We need to undo a former set with the same level
+			 * in order to get the expected result from
+			 * cli_credentials_get_principal().
+			 *
+			 * But we only need to do that if the domain
+			 * actually changes.
+			 */
+			cli_credentials_set_realm(credentials, domain, obtained);
+		}
+		cli_credentials_set_domain(credentials, domain, obtained);
+	}
+	if (obtained == credentials->principal_obtained &&
+	    !strequal_m(credentials->username, uname))
+	{
+		/*
+		 * We need to undo a former set with the same level
+		 * in order to get the expected result from
+		 * cli_credentials_get_principal().
+		 *
+		 * But we only need to do that if the username
+		 * actually changes.
+		 */
+		credentials->principal_obtained = CRED_UNINITIALISED;
+		credentials->principal = NULL;
 	}
 	cli_credentials_set_username(credentials, uname, obtained);
 }
-- 
1.9.1


From 11d088e621a9362e9af542e0579504dc0a51cec2 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 14:12:31 +0100
Subject: [PATCH 13/19] tests/credentials.py: add tests to verify
 realm/principal behaviour of cli_credentials_parse_string()

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 50 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 49 insertions(+), 1 deletion(-)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index 82a8ad9..d13d4df 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -210,7 +210,7 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
-    def test_parse_username_with_domain(self):
+    def test_parse_username_with_domain_0(self):
         creds = credentials.Credentials()
         lp = samba.tests.env_loadparm()
         os.environ["USER"] = "env_user"
@@ -223,6 +223,54 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(creds.is_anonymous(), False)
         self.assertEqual(creds.authentication_requested(), True)
 
+    def test_parse_username_with_domain_1(self):
+        creds = credentials.Credentials()
+        lp = samba.tests.env_loadparm()
+        os.environ["USER"] = "env_user"
+        creds.guess(lp)
+        realm = "realm.example.com"
+        creds.set_realm(realm, credentials.UNINITIALISED)
+        self.assertEqual(creds.get_username(), "env_user")
+        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_realm(), realm.upper())
+        self.assertEqual(creds.get_principal(), "env_user@%s" % realm.upper())
+        creds.set_principal("unknown at realm.example.com")
+        self.assertEqual(creds.get_username(), "env_user")
+        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_realm(), realm.upper())
+        self.assertEqual(creds.get_principal(), "unknown at realm.example.com")
+        creds.parse_string("domain\user")
+        self.assertEqual(creds.get_username(), "user")
+        self.assertEqual(creds.get_domain(), "DOMAIN")
+        self.assertEqual(creds.get_realm(), realm.upper())
+        self.assertEqual(creds.get_principal(), "user at DOMAIN")
+        self.assertEqual(creds.is_anonymous(), False)
+        self.assertEqual(creds.authentication_requested(), True)
+
+    def test_parse_username_with_domain_2(self):
+        creds = credentials.Credentials()
+        lp = samba.tests.env_loadparm()
+        os.environ["USER"] = "env_user"
+        creds.guess(lp)
+        realm = "realm.example.com"
+        creds.set_realm(realm, credentials.SPECIFIED)
+        self.assertEqual(creds.get_username(), "env_user")
+        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_realm(), realm.upper())
+        self.assertEqual(creds.get_principal(), "env_user@%s" % realm.upper())
+        creds.set_principal("unknown at realm.example.com")
+        self.assertEqual(creds.get_username(), "env_user")
+        self.assertEqual(creds.get_domain(), lp.get("workgroup").upper())
+        self.assertEqual(creds.get_realm(), realm.upper())
+        self.assertEqual(creds.get_principal(), "unknown at realm.example.com")
+        creds.parse_string("domain\user")
+        self.assertEqual(creds.get_username(), "user")
+        self.assertEqual(creds.get_domain(), "DOMAIN")
+        self.assertEqual(creds.get_realm(), "DOMAIN")
+        self.assertEqual(creds.get_principal(), "user at DOMAIN")
+        self.assertEqual(creds.is_anonymous(), False)
+        self.assertEqual(creds.authentication_requested(), True)
+
     def test_parse_username_with_realm(self):
         creds = credentials.Credentials()
         lp = samba.tests.env_loadparm()
-- 
1.9.1


From 51b765d66008f7184b08e6991ac713d5a38c8565 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Sun, 11 Dec 2016 22:50:53 +0100
Subject: [PATCH 14/19] auth/credentials: let cli_credentials_parse_file()
 handle 'username' with cli_credentials_parse_string()

Some existing source3 tests (test_smbclient_s3.sh test_auth_file()) use a credentials file
that looks like this:

  username=DOMAIN/username
  password=password
  domain=DOMAIN

This change allows us to parse the same.

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index 14c8403..0ffcc5c 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -1149,7 +1149,7 @@ _PUBLIC_ bool cli_credentials_parse_file(struct cli_credentials *cred, const cha
 		if (strwicmp("password", param) == 0) {
 			cli_credentials_set_password(cred, val, obtained);
 		} else if (strwicmp("username", param) == 0) {
-			cli_credentials_set_username(cred, val, obtained);
+			cli_credentials_parse_string(cred, val, obtained);
 		} else if (strwicmp("domain", param) == 0) {
 			cli_credentials_set_domain(cred, val, obtained);
 		} else if (strwicmp("realm", param) == 0) {
-- 
1.9.1


From 11773e4c1a30d37d8dfa86751b1182270980915f Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 14:01:35 +0100
Subject: [PATCH 15/19] tests/credentials.py: verify the new
 cli_credentials_parse_file() 'username' parsing

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 49 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 49 insertions(+)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index d13d4df..7aaa711 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -178,6 +178,55 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(self.creds.get_password(), password)
         self.assertEqual(self.creds.get_domain(), domain.upper())
         self.assertEqual(self.creds.get_realm(), realm.upper())
+        self.assertEqual(self.creds.get_principal(), "%s@%s" % (username, realm.upper()))
+        self.assertEqual(self.creds.is_anonymous(), False)
+        self.assertEqual(self.creds.authentication_requested(), True)
+        os.unlink(passwd_file_name)
+
+    def test_parse_file_2(self):
+        realm="realm.example.com"
+        domain="dom"
+        password="pass"
+        username="user"
+
+        passwd_file_name = os.path.join(self.tempdir, "parse_file")
+        passwd_file_fd = open(passwd_file_name, 'wx')
+        passwd_file_fd.write("realm=%s\n" % realm)
+        passwd_file_fd.write("domain=%s\n" % domain)
+        passwd_file_fd.write("username=%s\\%s\n" % (domain, username))
+        passwd_file_fd.write("password=%s\n" % password)
+        passwd_file_fd.close()
+        self.creds.parse_file(passwd_file_name)
+        self.assertEqual(self.creds.get_username(), username)
+        self.assertEqual(self.creds.get_password(), password)
+        self.assertEqual(self.creds.get_domain(), domain.upper())
+        self.assertEqual(self.creds.get_realm(), realm.upper())
+        self.assertEqual(self.creds.get_principal(), "%s@%s" % (username, realm.upper()))
+        self.assertEqual(self.creds.is_anonymous(), False)
+        self.assertEqual(self.creds.authentication_requested(), True)
+        os.unlink(passwd_file_name)
+
+    def test_parse_file_3(self):
+        realm="realm.example.com"
+        domain="domain"
+        password="password"
+        username="username"
+
+        userdom="userdom"
+
+        passwd_file_name = os.path.join(self.tempdir, "parse_file")
+        passwd_file_fd = open(passwd_file_name, 'wx')
+        passwd_file_fd.write("realm=%s\n" % realm)
+        passwd_file_fd.write("domain=%s\n" % domain)
+        passwd_file_fd.write("username=%s/%s\n" % (userdom, username))
+        passwd_file_fd.write("password=%s\n" % password)
+        passwd_file_fd.close()
+        self.creds.parse_file(passwd_file_name)
+        self.assertEqual(self.creds.get_username(), username)
+        self.assertEqual(self.creds.get_password(), password)
+        self.assertEqual(self.creds.get_domain(), userdom.upper())
+        self.assertEqual(self.creds.get_realm(), userdom.upper())
+        self.assertEqual(self.creds.get_principal(), "%s@%s" % (username, userdom.upper()))
         self.assertEqual(self.creds.is_anonymous(), False)
         self.assertEqual(self.creds.authentication_requested(), True)
         os.unlink(passwd_file_name)
-- 
1.9.1


From c296d7f38cc8be1b689d4251be55c0c5cac501e4 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 12:41:58 +0100
Subject: [PATCH 16/19] auth/credentials: change the parsing order of
 cli_credentials_parse_file()

We now first just remember the domain, realm, username, password values
(the last value wins).

At the end we call cli_credentials_set_{realm,domain,password}()
followed by cli_credentials_parse_string() for 'username'.

It means the last 'username' line beats the domain, realm or password lines, e.g.:

 username=USERDOMAIN\username
 domain=DOMAIN

will result in cli_credentials_get_domain() returning "USERDOMAIN" instead of
DOMAIN.

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 auth/credentials/credentials.c | 54 ++++++++++++++++++++++++++++++++++++++----
 1 file changed, 49 insertions(+), 5 deletions(-)

diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
index 0ffcc5c..9a935c6 100644
--- a/auth/credentials/credentials.c
+++ b/auth/credentials/credentials.c
@@ -1117,6 +1117,10 @@ _PUBLIC_ bool cli_credentials_parse_file(struct cli_credentials *cred, const cha
 	char *ptr, *val, *param;
 	char **lines;
 	int i, numlines;
+	const char *realm = NULL;
+	const char *domain = NULL;
+	const char *password = NULL;
+	const char *username = NULL;
 
 	lines = file_lines_load(file, &numlines, 0, NULL);
 
@@ -1147,17 +1151,57 @@ _PUBLIC_ bool cli_credentials_parse_file(struct cli_credentials *cred, const cha
 			val++;
 
 		if (strwicmp("password", param) == 0) {
-			cli_credentials_set_password(cred, val, obtained);
+			password = val;
 		} else if (strwicmp("username", param) == 0) {
-			cli_credentials_parse_string(cred, val, obtained);
+			username = val;
 		} else if (strwicmp("domain", param) == 0) {
-			cli_credentials_set_domain(cred, val, obtained);
+			domain = val;
 		} else if (strwicmp("realm", param) == 0) {
-			cli_credentials_set_realm(cred, val, obtained);
+			realm = val;
 		}
-		memset(lines[i], 0, len);
+
+		/*
+		 * We need to readd '=' in order to let
+		 * the strlen() work in the last loop
+		 * that clears the memory.
+		 */
+		*ptr = '=';
+	}
+
+	if (realm != NULL && strlen(realm) != 0) {
+		/*
+		 * only overwrite with a valid string
+		 */
+		cli_credentials_set_realm(cred, realm, obtained);
+	}
+
+	if (domain != NULL && strlen(domain) != 0) {
+		/*
+		 * only overwrite with a valid string
+		 */
+		cli_credentials_set_domain(cred, domain, obtained);
 	}
 
+	if (password != NULL) {
+		/*
+		 * Here we allow "".
+		 */
+		cli_credentials_set_password(cred, password, obtained);
+	}
+
+	if (username != NULL) {
+		/*
+		 * The last "username" line takes preference
+		 * if the string also contains domain, realm or
+		 * password.
+		 */
+		cli_credentials_parse_string(cred, username, obtained);
+	}
+
+	for (i = 0; i < numlines; i++) {
+		len = strlen(lines[i]);
+		memset(lines[i], 0, len);
+	}
 	talloc_free(lines);
 
 	return true;
-- 
1.9.1


From 7f3d9e3758ab2037abea573f0bcb9151294a4b19 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Thu, 15 Dec 2016 15:30:28 +0100
Subject: [PATCH 17/19] tests/credentials.py: demonstrate the last 'username'
 line of creds.parse_file() beats other lines

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/credentials.py | 50 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

diff --git a/python/samba/tests/credentials.py b/python/samba/tests/credentials.py
index 7aaa711..4e5746e 100644
--- a/python/samba/tests/credentials.py
+++ b/python/samba/tests/credentials.py
@@ -231,6 +231,56 @@ class CredentialsTests(samba.tests.TestCaseInTempDir):
         self.assertEqual(self.creds.authentication_requested(), True)
         os.unlink(passwd_file_name)
 
+    def test_parse_file_4(self):
+        realm="realm.example.com"
+        domain="domain"
+        password="password"
+        username="username"
+
+        userdom="userdom"
+
+        passwd_file_name = os.path.join(self.tempdir, "parse_file")
+        passwd_file_fd = open(passwd_file_name, 'wx')
+        passwd_file_fd.write("username=%s\\%s%%%s\n" % (userdom, username, password))
+        passwd_file_fd.write("realm=ignorerealm\n")
+        passwd_file_fd.write("domain=ignoredomain\n")
+        passwd_file_fd.write("password=ignorepassword\n")
+        passwd_file_fd.close()
+        self.creds.parse_file(passwd_file_name)
+        self.assertEqual(self.creds.get_username(), username)
+        self.assertEqual(self.creds.get_password(), password)
+        self.assertEqual(self.creds.get_domain(), userdom.upper())
+        self.assertEqual(self.creds.get_realm(), userdom.upper())
+        self.assertEqual(self.creds.get_principal(), "%s@%s" % (username, userdom.upper()))
+        self.assertEqual(self.creds.is_anonymous(), False)
+        self.assertEqual(self.creds.authentication_requested(), True)
+        os.unlink(passwd_file_name)
+
+    def test_parse_file_5(self):
+        realm="realm.example.com"
+        domain="domain"
+        password="password"
+        username="username"
+
+        userdom="userdom"
+
+        passwd_file_name = os.path.join(self.tempdir, "parse_file")
+        passwd_file_fd = open(passwd_file_name, 'wx')
+        passwd_file_fd.write("realm=ignorerealm\n")
+        passwd_file_fd.write("username=%s\\%s%%%s\n" % (userdom, username, password))
+        passwd_file_fd.write("domain=ignoredomain\n")
+        passwd_file_fd.write("password=ignorepassword\n")
+        passwd_file_fd.close()
+        self.creds.parse_file(passwd_file_name)
+        self.assertEqual(self.creds.get_username(), username)
+        self.assertEqual(self.creds.get_password(), password)
+        self.assertEqual(self.creds.get_domain(), userdom.upper())
+        self.assertEqual(self.creds.get_realm(), userdom.upper())
+        self.assertEqual(self.creds.get_principal(), "%s@%s" % (username, userdom.upper()))
+        self.assertEqual(self.creds.is_anonymous(), False)
+        self.assertEqual(self.creds.authentication_requested(), True)
+        os.unlink(passwd_file_name)
+
     def test_parse_username_0(self):
         creds = credentials.Credentials()
         lp = samba.tests.env_loadparm()
-- 
1.9.1


From a712bce67f1a6eddcb56f805221f3dcccc351938 Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Fri, 9 Dec 2016 16:04:38 +0100
Subject: [PATCH 18/19] s3:popt_common: let POPT_COMMON_CREDENTIALS imply
 logfile and conffile loading

All users of POPT_COMMON_CREDENTIALS basically need the same logic,
while some ignore a broken smb.conf and some complain about it.

This will allow the future usage of config options in the
credential post processing.

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/client/client.c       | 11 +----------
 source3/include/popt_common.h |  1 +
 source3/lib/popt_common.c     | 25 +++++++++++++++++++++++++
 source3/rpcclient/rpcclient.c | 15 ---------------
 source3/utils/regedit.c       |  5 -----
 source3/utils/smbcacls.c      |  5 ++---
 source3/utils/smbcquotas.c    | 12 ------------
 source3/utils/smbtree.c       |  5 ++---
 8 files changed, 31 insertions(+), 48 deletions(-)

diff --git a/source3/client/client.c b/source3/client/client.c
index 82521288..cde9776 100644
--- a/source3/client/client.c
+++ b/source3/client/client.c
@@ -5765,6 +5765,7 @@ int main(int argc,char *argv[])
 
 	lp_set_cmdline("log level", "1");
 
+	popt_common_credentials_set_ignore_missing_conf();
 	popt_common_credentials_set_delay_post();
 
 	/* skip argv(0) */
@@ -5901,16 +5902,6 @@ int main(int argc,char *argv[])
 					       poptGetArg(pc));
 	}
 
-	if ( override_logfile )
-		setup_logging( lp_logfile(talloc_tos()), DEBUG_FILE );
-
-	if (!lp_load_client(get_dyn_CONFIGFILE())) {
-		fprintf(stderr, "%s: Can't load %s - run testparm to debug it\n",
-			argv[0], get_dyn_CONFIGFILE());
-	}
-
-	load_interfaces();
-
 	if (service_opt && service) {
 		size_t len;
 
diff --git a/source3/include/popt_common.h b/source3/include/popt_common.h
index ec7568c..2abfa04 100644
--- a/source3/include/popt_common.h
+++ b/source3/include/popt_common.h
@@ -50,6 +50,7 @@ extern const struct poptOption popt_common_dynconfig[];
 
 extern struct user_auth_info *cmdline_auth_info;
 
+void popt_common_credentials_set_ignore_missing_conf(void);
 void popt_common_credentials_set_delay_post(void);
 void popt_common_credentials_post(void);
 void popt_burn_cmdline_password(int argc, char *argv[]);
diff --git a/source3/lib/popt_common.c b/source3/lib/popt_common.c
index b2fd39c..e229ad2 100644
--- a/source3/lib/popt_common.c
+++ b/source3/lib/popt_common.c
@@ -348,8 +348,14 @@ static void get_credentials_file(struct user_auth_info *auth_info,
  */
 
 struct user_auth_info *cmdline_auth_info;
+static bool popt_common_credentials_ignore_missing_conf;
 static bool popt_common_credentials_delay_post;
 
+void popt_common_credentials_set_ignore_missing_conf(void)
+{
+	popt_common_credentials_delay_post = true;
+}
+
 void popt_common_credentials_set_delay_post(void)
 {
 	popt_common_credentials_delay_post = true;
@@ -412,6 +418,25 @@ static void popt_common_credentials_callback(poptContext con,
 	}
 
 	if (reason == POPT_CALLBACK_REASON_POST) {
+		bool ok;
+
+		if (override_logfile) {
+			setup_logging(lp_logfile(talloc_tos()), DEBUG_FILE );
+		}
+
+		ok = lp_load_client(get_dyn_CONFIGFILE());
+		if (!ok) {
+			const char *pname = poptGetInvocationName(con);
+
+			fprintf(stderr, "%s: Can't load %s - run testparm to debug it\n",
+				pname, get_dyn_CONFIGFILE());
+			if (!popt_common_credentials_ignore_missing_conf) {
+				exit(1);
+			}
+		}
+
+		load_interfaces();
+
 		if (popt_common_credentials_delay_post) {
 			return;
 		}
diff --git a/source3/rpcclient/rpcclient.c b/source3/rpcclient/rpcclient.c
index 138fdba..9faf01b 100644
--- a/source3/rpcclient/rpcclient.c
+++ b/source3/rpcclient/rpcclient.c
@@ -949,7 +949,6 @@ out_free:
 	char *user, *domain, *q;
 	const char *host;
 	int signing_state = SMB_SIGNING_IPC_DEFAULT;
-	bool ok;
 
 	/* make sure the vars that get altered (4th field) are in
 	   a fixed location or certain compilers complain */
@@ -1016,17 +1015,6 @@ out_free:
 	popt_burn_cmdline_password(argc, argv);
 	rpcclient_auth_info = cmdline_auth_info;
 
-	/* Load smb.conf file */
-
-	ok = lp_load_global(get_dyn_CONFIGFILE());
-	if (!ok) {
-		fprintf(stderr,
-			"Can't load %s - run testparm to debug it\n",
-			get_dyn_CONFIGFILE());
-		result = 1;
-		goto done;
-	}
-
 	nt_status = messaging_init_client(talloc_autofree_context(),
 					  samba_tevent_context_init(talloc_autofree_context()),
 					  &rpcclient_msg_ctx);
@@ -1045,9 +1033,6 @@ out_free:
 		goto done;
 	}
 
-	/* We must load interfaces after we load the smb.conf */
-	load_interfaces();
-
 	if (!init_names()) {
 		result = 1;
 		goto done;
diff --git a/source3/utils/regedit.c b/source3/utils/regedit.c
index 476897f..14e75c2 100644
--- a/source3/utils/regedit.c
+++ b/source3/utils/regedit.c
@@ -781,11 +781,6 @@ int main(int argc, const char **argv)
 		/* TODO */
 	}
 
-	if (!lp_load_global(get_dyn_CONFIGFILE())) {
-		DEBUG(0, ("ERROR loading config file...\n"));
-		exit(1);
-	}
-
 	rv = reg_open_samba3(frame, &ctx);
 	if (!W_ERROR_IS_OK(rv)) {
 		fprintf(stderr, "Unable to open registry: %s\n",
diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
index 7705eb4..255ff97 100644
--- a/source3/utils/smbcacls.c
+++ b/source3/utils/smbcacls.c
@@ -831,6 +831,8 @@ int main(int argc, char *argv[])
 
 	setlinebuf(stdout);
 
+	popt_common_credentials_set_ignore_missing_conf();
+
 	pc = poptGetContext("smbcacls", argc, argv_const, long_options, 0);
 
 	poptSetOtherOptionHelp(pc, "//server1/share1 filename\nACLs look like: "
@@ -894,9 +896,6 @@ int main(int argc, char *argv[])
 		return -1;
 	}
 
-	lp_load_global(get_dyn_CONFIGFILE());
-	load_interfaces();
-
 	filename = talloc_strdup(frame, poptGetArg(pc));
 	if (!filename) {
 		return -1;
diff --git a/source3/utils/smbcquotas.c b/source3/utils/smbcquotas.c
index ba80926..a1cf70a1 100644
--- a/source3/utils/smbcquotas.c
+++ b/source3/utils/smbcquotas.c
@@ -575,7 +575,6 @@ int main(int argc, char *argv[])
 	static bool test_args = False;
 	struct cli_state *cli;
 	bool fix_user = False;
-	bool ok;
 	SMB_NTQUOTA_STRUCT qt;
 	TALLOC_CTX *frame = talloc_stackframe();
 	poptContext pc;
@@ -700,17 +699,6 @@ FSQFLAGS:QUOTA_ENABLED/DENY_DISK/LOG_SOFTLIMIT/LOG_HARD_LIMIT", "SETSTRING" },
 	poptFreeContext(pc);
 	popt_burn_cmdline_password(argc, argv);
 
-	ok = lp_load_global(get_dyn_CONFIGFILE());
-	if (!ok) {
-		DBG_ERR("ERROR: Loading config file %s - "
-			"run testparm to debug it\n",
-			get_dyn_CONFIGFILE());
-		exit(EXIT_PARSE_ERROR);
-	}
-
-	/* We must load interfaces after we load the smb.conf */
-	load_interfaces();
-
 	string_replace(path, '/', '\\');
 
 	server = SMB_STRDUP(path+2);
diff --git a/source3/utils/smbtree.c b/source3/utils/smbtree.c
index 2f11dce..ba7b318 100644
--- a/source3/utils/smbtree.c
+++ b/source3/utils/smbtree.c
@@ -307,15 +307,14 @@ int main(int argc, char *argv[])
 
 	setup_logging(argv[0], DEBUG_STDERR);
 
+	popt_common_credentials_set_ignore_missing_conf();
+
 	pc = poptGetContext("smbtree", argc, argv_const, long_options,
 			    POPT_CONTEXT_KEEP_FIRST);
 	while(poptGetNextOpt(pc) != -1);
 	poptFreeContext(pc);
 	popt_burn_cmdline_password(argc, argv);
 
-	lp_load_global(get_dyn_CONFIGFILE());
-	load_interfaces();
-
 	/* Now do our stuff */
 
         if (!print_tree(cmdline_auth_info)) {
-- 
1.9.1


From 922fc219f1b891818f2d8b880f5033323ae7d28d Mon Sep 17 00:00:00 2001
From: Stefan Metzmacher <metze at samba.org>
Date: Fri, 28 Oct 2016 12:14:37 +0200
Subject: [PATCH 19/19] s3:user_auth_info: let struct user_auth_info use struct
 cli_credentials internally

This way we can have a very simple get_cmdline_auth_info_creds() function,
which can be used pass cli_credentials down the stack instead of
constantly translating from user_auth_info to cli_credentials, while
loosing information.

Signed-off-by: Stefan Metzmacher <metze at samba.org>
---
 source3/include/auth_info.h |   6 +
 source3/lib/popt_common.c   | 182 +----------------------
 source3/lib/util_cmdline.c  | 346 +++++++++++++++++++++++++++++---------------
 3 files changed, 245 insertions(+), 289 deletions(-)

diff --git a/source3/include/auth_info.h b/source3/include/auth_info.h
index 04ac422..c6f71ad 100644
--- a/source3/include/auth_info.h
+++ b/source3/include/auth_info.h
@@ -23,6 +23,9 @@
 struct user_auth_info;
 
 struct user_auth_info *user_auth_info_init(TALLOC_CTX *mem_ctx);
+void set_cmdline_auth_info_guess(struct user_auth_info *auth_info);
+void set_cmdline_auth_info_from_file(struct user_auth_info *auth_info,
+				     const char *filename);
 const char *get_cmdline_auth_info_username(const struct user_auth_info *auth_info);
 void set_cmdline_auth_info_username(struct user_auth_info *auth_info,
 				    const char *username);
@@ -59,4 +62,7 @@ bool get_cmdline_auth_info_use_machine_account(const struct user_auth_info *auth
 bool set_cmdline_auth_info_machine_account_creds(struct user_auth_info *auth_info);
 void set_cmdline_auth_info_getpass(struct user_auth_info *auth_info);
 
+struct cli_credentials *get_cmdline_auth_info_creds(
+	const struct user_auth_info *auth_info);
+
 #endif /* _AUTH_INFO_H */
diff --git a/source3/lib/popt_common.c b/source3/lib/popt_common.c
index e229ad2..3589a4f 100644
--- a/source3/lib/popt_common.c
+++ b/source3/lib/popt_common.c
@@ -21,7 +21,6 @@
 */
 
 #include "includes.h"
-#include "system/filesys.h"
 #include "popt_common.h"
 #include "lib/param/param.h"
 
@@ -211,131 +210,6 @@ struct poptOption popt_common_option[] = {
 	POPT_TABLEEND
 };
 
-/****************************************************************************
- * get a password from a a file or file descriptor
- * exit on failure
- * ****************************************************************************/
-
-static void get_password_file(struct user_auth_info *auth_info)
-{
-	int fd = -1;
-	char *p;
-	bool close_it = False;
-	char *spec = NULL;
-	char pass[128];
-
-	if ((p = getenv("PASSWD_FD")) != NULL) {
-		if (asprintf(&spec, "descriptor %s", p) < 0) {
-			return;
-		}
-		sscanf(p, "%d", &fd);
-		close_it = false;
-	} else if ((p = getenv("PASSWD_FILE")) != NULL) {
-		fd = open(p, O_RDONLY, 0);
-		spec = SMB_STRDUP(p);
-		if (fd < 0) {
-			fprintf(stderr, "Error opening PASSWD_FILE %s: %s\n",
-					spec, strerror(errno));
-			exit(1);
-		}
-		close_it = True;
-	}
-
-	if (fd < 0) {
-		fprintf(stderr, "fd = %d, < 0\n", fd);
-		exit(1);
-	}
-
-	for(p = pass, *p = '\0'; /* ensure that pass is null-terminated */
-		p && p - pass < sizeof(pass);) {
-		switch (read(fd, p, 1)) {
-		case 1:
-			if (*p != '\n' && *p != '\0') {
-				*++p = '\0'; /* advance p, and null-terminate pass */
-				break;
-			}
-		case 0:
-			if (p - pass) {
-				*p = '\0'; /* null-terminate it, just in case... */
-				p = NULL; /* then force the loop condition to become false */
-				break;
-			} else {
-				fprintf(stderr, "Error reading password from file %s: %s\n",
-						spec, "empty password\n");
-				SAFE_FREE(spec);
-				exit(1);
-			}
-
-		default:
-			fprintf(stderr, "Error reading password from file %s: %s\n",
-					spec, strerror(errno));
-			SAFE_FREE(spec);
-			exit(1);
-		}
-	}
-	SAFE_FREE(spec);
-
-	set_cmdline_auth_info_password(auth_info, pass);
-	if (close_it) {
-		close(fd);
-	}
-}
-
-static void get_credentials_file(struct user_auth_info *auth_info,
-				 const char *file)
-{
-	FILE *auth;
-	fstring buf;
-	uint16_t len = 0;
-	char *ptr, *val, *param;
-
-	auth = fopen(file, "r");
-	if (auth == NULL) {
-		/* fail if we can't open the credentials file */
-		d_printf("ERROR: Unable to open credentials file!\n");
-		exit(-1);
-	}
-
-	while (!feof(auth))
-	{
-		/* get a line from the file */
-		if (!fgets(buf, sizeof(buf), auth))
-			continue;
-		len = strlen(buf);
-
-		if ((len) && (buf[len-1]=='\n'))
-		{
-			buf[len-1] = '\0';
-			len--;
-		}
-		if (len == 0)
-			continue;
-
-		/* break up the line into parameter & value.
-		 * will need to eat a little whitespace possibly */
-		param = buf;
-		if (!(ptr = strchr_m (buf, '=')))
-			continue;
-
-		val = ptr+1;
-		*ptr = '\0';
-
-		/* eat leading white space */
-		while ((*val!='\0') && ((*val==' ') || (*val=='\t')))
-			val++;
-
-		if (strwicmp("password", param) == 0) {
-			set_cmdline_auth_info_password(auth_info, val);
-		} else if (strwicmp("username", param) == 0) {
-			set_cmdline_auth_info_username(auth_info, val);
-		} else if (strwicmp("domain", param) == 0) {
-			set_cmdline_auth_info_domain(auth_info, val);
-		}
-		memset(buf, 0, sizeof(buf));
-	}
-	fclose(auth);
-}
-
 /* Handle command line options:
  *		-U,--user
  *		-A,--authentication-file
@@ -384,36 +258,12 @@ static void popt_common_credentials_callback(poptContext con,
 	struct user_auth_info *auth_info = cmdline_auth_info;
 
 	if (reason == POPT_CALLBACK_REASON_PRE) {
+		auth_info = user_auth_info_init(talloc_autofree_context());
 		if (auth_info == NULL) {
-			auth_info = user_auth_info_init(talloc_autofree_context());
-			if (auth_info == NULL) {
-				fprintf(stderr, "user_auth_info_init() failed\n");
-				exit(1);
-			}
-			cmdline_auth_info = auth_info;
-		}
-
-		set_cmdline_auth_info_username(auth_info, "GUEST");
-
-		if (getenv("LOGNAME")) {
-			set_cmdline_auth_info_username(auth_info,
-						       getenv("LOGNAME"));
-		}
-
-		if (getenv("USER")) {
-			set_cmdline_auth_info_username(auth_info,
-						       getenv("USER"));
-		}
-
-		if (getenv("PASSWD")) {
-			set_cmdline_auth_info_password(auth_info,
-						       getenv("PASSWD"));
-		}
-
-		if (getenv("PASSWD_FD") || getenv("PASSWD_FILE")) {
-			get_password_file(auth_info);
+			fprintf(stderr, "user_auth_info_init() failed\n");
+			exit(1);
 		}
-
+		cmdline_auth_info = auth_info;
 		return;
 	}
 
@@ -437,6 +287,8 @@ static void popt_common_credentials_callback(poptContext con,
 
 		load_interfaces();
 
+		set_cmdline_auth_info_guess(auth_info);
+
 		if (popt_common_credentials_delay_post) {
 			return;
 		}
@@ -447,29 +299,11 @@ static void popt_common_credentials_callback(poptContext con,
 
 	switch(opt->val) {
 	case 'U':
-		{
-			char *lp;
-			char *puser = SMB_STRDUP(arg);
-
-			if ((lp=strchr_m(puser,'%'))) {
-				size_t len;
-				*lp = '\0';
-				set_cmdline_auth_info_username(auth_info,
-							       puser);
-				set_cmdline_auth_info_password(auth_info,
-							       lp+1);
-				len = strlen(lp+1);
-				memset(lp + 1, '\0', len);
-			} else {
-				set_cmdline_auth_info_username(auth_info,
-							       puser);
-			}
-			SAFE_FREE(puser);
-		}
+		set_cmdline_auth_info_username(auth_info, arg);
 		break;
 
 	case 'A':
-		get_credentials_file(auth_info, arg);
+		set_cmdline_auth_info_from_file(auth_info, arg);
 		break;
 
 	case 'k':
diff --git a/source3/lib/util_cmdline.c b/source3/lib/util_cmdline.c
index 68ba7aa..6c98b44 100644
--- a/source3/lib/util_cmdline.c
+++ b/source3/lib/util_cmdline.c
@@ -24,6 +24,10 @@
 #include "includes.h"
 #include "auth_info.h"
 #include "secrets.h"
+#include "param/param.h"
+#include "librpc/gen_ndr/samr.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
 
 /**************************************************************************n
   Code to cope with username/password auth options from the commandline.
@@ -31,129 +35,174 @@
 ****************************************************************************/
 
 struct user_auth_info {
-	char *username;
-	char *domain;
-	char *password;
+	struct cli_credentials *creds;
+	struct loadparm_context *lp_ctx;
 	bool got_pass;
-	bool use_kerberos;
 	int signing_state;
 	bool smb_encrypt;
 	bool use_machine_account;
-	bool fallback_after_kerberos;
-	bool use_ccache;
 	bool use_pw_nt_hash;
+	char *pw_nt_hash;
 };
 
 struct user_auth_info *user_auth_info_init(TALLOC_CTX *mem_ctx)
 {
-	struct user_auth_info *result;
+	struct user_auth_info *result = NULL;
 
 	result = talloc_zero(mem_ctx, struct user_auth_info);
 	if (result == NULL) {
 		return NULL;
 	}
 
+	result->lp_ctx = loadparm_init_s3(result, loadparm_s3_helpers());
+	if (result->lp_ctx == NULL) {
+		TALLOC_FREE(result);
+		return NULL;
+	}
+
+	result->creds = cli_credentials_init(result);
+	if (result->creds == NULL) {
+		TALLOC_FREE(result);
+		return NULL;
+	}
+
+	cli_credentials_set_conf(result->creds, result->lp_ctx);
+
 	result->signing_state = SMB_SIGNING_DEFAULT;
 	return result;
 }
 
-const char *get_cmdline_auth_info_username(const struct user_auth_info *auth_info)
+void set_cmdline_auth_info_guess(struct user_auth_info *auth_info)
 {
-	if (!auth_info->username) {
-		return "";
-	}
-	return auth_info->username;
+	/*
+	 * Note that cli_credentials_guess() calls
+	 * cli_credentials_set_conf() again, which will
+	 * hopefully cope with a reloaded smb.conf.
+	 */
+	cli_credentials_set_username(auth_info->creds, "GUEST", CRED_GUESS_ENV);
+	cli_credentials_guess(auth_info->creds, auth_info->lp_ctx);
 }
 
-void set_cmdline_auth_info_username(struct user_auth_info *auth_info,
-				    const char *username)
+void set_cmdline_auth_info_from_file(struct user_auth_info *auth_info,
+				     const char *filename)
 {
-	char *s;
-	char *p;
-	bool contains_domain = false;
-
-	s = talloc_strdup(auth_info, username);
-	if (s == NULL) {
-		exit(ENOMEM);
-	}
+	bool ok;
 
-	p = strchr_m(s, '\\');
-	if (p != NULL) {
-		contains_domain = true;
-	}
-	if (!contains_domain) {
-		p = strchr_m(s, '/');
-		if (p != NULL) {
-			contains_domain = true;
-		}
-	}
-	if (!contains_domain) {
-		char sep = *lp_winbind_separator();
-
-		if (sep != '\0') {
-			p = strchr_m(s, *lp_winbind_separator());
-			if (p != NULL) {
-				contains_domain = true;
-			}
-		}
+	ok = cli_credentials_parse_file(auth_info->creds, filename,
+					CRED_SPECIFIED);
+	if (!ok) {
+		exit(EIO);
 	}
+}
 
-	if (contains_domain) {
-		*p = '\0';
-		username = p + 1;
+const char *get_cmdline_auth_info_username(const struct user_auth_info *auth_info)
+{
+	const char *username = NULL;
 
-		/* s is now the workgroup part */
-		set_cmdline_auth_info_domain(auth_info, s);
+	username = cli_credentials_get_username(auth_info->creds);
+	if (username == NULL) {
+		return "";
 	}
 
-	TALLOC_FREE(auth_info->username);
-	auth_info->username = talloc_strdup(auth_info, username);
+	return username;
+}
 
-	TALLOC_FREE(s);
+void set_cmdline_auth_info_username(struct user_auth_info *auth_info,
+				    const char *username)
+{
+	const char *new_val = NULL;
 
-	if (!auth_info->username) {
+	cli_credentials_parse_string(auth_info->creds,
+				     username,
+				     CRED_SPECIFIED);
+	new_val = cli_credentials_get_username(auth_info->creds);
+	if (username != NULL && new_val == NULL) {
 		exit(ENOMEM);
 	}
+
+	if (strchr_m(username, '%') != NULL) {
+		auth_info->got_pass = true;
+	}
 }
 
 const char *get_cmdline_auth_info_domain(const struct user_auth_info *auth_info)
 {
-	if (!auth_info->domain) {
+	const char *domain = NULL;
+
+	domain = cli_credentials_get_domain(auth_info->creds);
+	if (domain == NULL) {
 		return "";
 	}
-	return auth_info->domain;
+
+	return domain;
 }
 
 void set_cmdline_auth_info_domain(struct user_auth_info *auth_info,
 				  const char *domain)
 {
-	TALLOC_FREE(auth_info->domain);
-	auth_info->domain = talloc_strdup(auth_info, domain);
-	if (!auth_info->domain) {
+	bool ok;
+
+	ok = cli_credentials_set_domain(auth_info->creds, domain, CRED_SPECIFIED);
+	if (!ok) {
 		exit(ENOMEM);
 	}
 }
 
 const char *get_cmdline_auth_info_password(const struct user_auth_info *auth_info)
 {
-	if (!auth_info->password) {
+	const char *password = NULL;
+
+	if (auth_info->pw_nt_hash != NULL) {
+		return auth_info->pw_nt_hash;
+	}
+
+	if (auth_info->use_pw_nt_hash) {
+		struct user_auth_info *ai =
+			discard_const_p(struct user_auth_info, auth_info);
+		struct samr_Password *nt_hash = NULL;
+
+		nt_hash = cli_credentials_get_nt_hash(ai->creds,
+						      ai);
+		if (nt_hash == NULL) {
+			return "";
+		}
+
+		ai->pw_nt_hash = hex_encode_talloc(ai,
+						   nt_hash->hash,
+						   sizeof(nt_hash->hash));
+		TALLOC_FREE(nt_hash);
+		if (ai->pw_nt_hash == NULL) {
+			return "";
+		}
+
+		return auth_info->pw_nt_hash;
+	}
+
+	password = cli_credentials_get_password(auth_info->creds);
+	if (password == NULL) {
 		return "";
 	}
-	return auth_info->password;
+
+	return password;
 }
 
 void set_cmdline_auth_info_password(struct user_auth_info *auth_info,
 				    const char *password)
 {
-	TALLOC_FREE(auth_info->password);
-	if (password == NULL) {
-		password = "";
+	bool ok;
+
+	auth_info->got_pass = true;
+
+	if (password != NULL && strlen(password) == 0) {
+		password = NULL;
 	}
-	auth_info->password = talloc_strdup(auth_info, password);
-	if (!auth_info->password) {
+
+	ok = cli_credentials_set_password(auth_info->creds,
+					  password,
+					  CRED_SPECIFIED);
+	if (!ok) {
 		exit(ENOMEM);
 	}
-	auth_info->got_pass = true;
 }
 
 bool set_cmdline_auth_info_signing_state(struct user_auth_info *auth_info,
@@ -189,18 +238,31 @@ int get_cmdline_auth_info_signing_state(const struct user_auth_info *auth_info)
 
 void set_cmdline_auth_info_use_ccache(struct user_auth_info *auth_info, bool b)
 {
-        auth_info->use_ccache = b;
+	uint32_t gensec_features;
+
+	gensec_features = cli_credentials_get_gensec_features(auth_info->creds);
+	gensec_features |= GENSEC_FEATURE_NTLM_CCACHE;
+	cli_credentials_set_gensec_features(auth_info->creds, gensec_features);
 }
 
 bool get_cmdline_auth_info_use_ccache(const struct user_auth_info *auth_info)
 {
-	return auth_info->use_ccache;
+	uint32_t gensec_features;
+
+	gensec_features = cli_credentials_get_gensec_features(auth_info->creds);
+	if (gensec_features & GENSEC_FEATURE_NTLM_CCACHE) {
+		return true;
+	}
+
+	return false;
 }
 
 void set_cmdline_auth_info_use_pw_nt_hash(struct user_auth_info *auth_info,
 					  bool b)
 {
+	TALLOC_FREE(auth_info->pw_nt_hash);
 	auth_info->use_pw_nt_hash = b;
+	cli_credentials_set_password_will_be_nt_hash(auth_info->creds, b);
 }
 
 bool get_cmdline_auth_info_use_pw_nt_hash(
@@ -212,29 +274,73 @@ bool get_cmdline_auth_info_use_pw_nt_hash(
 void set_cmdline_auth_info_use_kerberos(struct user_auth_info *auth_info,
 					bool b)
 {
-        auth_info->use_kerberos = b;
+	enum credentials_use_kerberos krb5_state;
+
+	if (b) {
+		krb5_state = CRED_MUST_USE_KERBEROS;
+	} else {
+		krb5_state = CRED_DONT_USE_KERBEROS;
+	}
+
+	cli_credentials_set_kerberos_state(auth_info->creds, krb5_state);
 }
 
 bool get_cmdline_auth_info_use_kerberos(const struct user_auth_info *auth_info)
 {
-	return auth_info->use_kerberos;
+	enum credentials_use_kerberos krb5_state;
+
+	krb5_state = cli_credentials_get_kerberos_state(auth_info->creds);
+
+	if (krb5_state == CRED_MUST_USE_KERBEROS) {
+		return true;
+	}
+
+	return false;
 }
 
 void set_cmdline_auth_info_fallback_after_kerberos(struct user_auth_info *auth_info,
 					bool b)
 {
-	auth_info->fallback_after_kerberos = b;
+	enum credentials_use_kerberos krb5_state;
+
+	krb5_state = cli_credentials_get_kerberos_state(auth_info->creds);
+
+	switch (krb5_state) {
+	case CRED_MUST_USE_KERBEROS:
+		if (b) {
+			krb5_state = CRED_AUTO_USE_KERBEROS;
+		}
+		break;
+	case CRED_AUTO_USE_KERBEROS:
+		if (!b) {
+			krb5_state = CRED_MUST_USE_KERBEROS;
+		}
+		break;
+	case CRED_DONT_USE_KERBEROS:
+		/* nothing to do */
+		break;
+	}
+
+	cli_credentials_set_kerberos_state(auth_info->creds, krb5_state);
 }
 
 bool get_cmdline_auth_info_fallback_after_kerberos(const struct user_auth_info *auth_info)
 {
-	return auth_info->fallback_after_kerberos;
+	enum credentials_use_kerberos krb5_state;
+
+	krb5_state = cli_credentials_get_kerberos_state(auth_info->creds);
+
+	if (krb5_state == CRED_AUTO_USE_KERBEROS) {
+		return true;
+	}
+
+	return false;
 }
 
 /* This should only be used by lib/popt_common.c JRA */
 void set_cmdline_auth_info_use_krb5_ticket(struct user_auth_info *auth_info)
 {
-	auth_info->use_kerberos = true;
+	set_cmdline_auth_info_use_kerberos(auth_info, true);
 	auth_info->got_pass = true;
 }
 
@@ -246,6 +352,8 @@ void set_cmdline_auth_info_smb_encrypt(struct user_auth_info *auth_info)
 
 void set_cmdline_auth_info_use_machine_account(struct user_auth_info *auth_info)
 {
+	cli_credentials_set_machine_account_pending(auth_info->creds,
+						    auth_info->lp_ctx);
 	auth_info->use_machine_account = true;
 }
 
@@ -266,54 +374,67 @@ bool get_cmdline_auth_info_use_machine_account(const struct user_auth_info *auth
 
 bool set_cmdline_auth_info_machine_account_creds(struct user_auth_info *auth_info)
 {
-	char *pass = NULL;
-	char *account = NULL;
-	const char *realm = lp_realm();
-	int rc;
-
+	struct db_context *db_ctx = NULL;
+	NTSTATUS status;
 
 	if (!get_cmdline_auth_info_use_machine_account(auth_info)) {
 		return false;
 	}
 
-	if (!secrets_init()) {
+	db_ctx = secrets_db_ctx();
+	if (db_ctx == NULL) {
 		d_printf("ERROR: Unable to open secrets database\n");
 		return false;
 	}
 
-	if (realm != NULL && realm[0] != '\0') {
-		rc = asprintf(&account,
-			      "%s$@%s",
-			      lp_netbios_name(),
-			      realm);
-		if (rc < 0) {
-			return false;
-		}
-	} else {
-		rc = asprintf(&account,
-			      "%s$",
-			      lp_netbios_name());
-		if (rc < 0) {
-			return false;
-		}
-	}
+	cli_credentials_set_domain(auth_info->creds, lpcfg_workgroup(auth_info->lp_ctx),
+				   CRED_SPECIFIED);
 
-	pass = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
-	if (!pass) {
+	status = cli_credentials_set_machine_account_db_ctx(auth_info->creds,
+							    auth_info->lp_ctx,
+							    db_ctx);
+	if (!NT_STATUS_IS_OK(status)) {
 		d_printf("ERROR: Unable to fetch machine password for "
-			"%s in domain %s\n",
-			account, lp_workgroup());
-		SAFE_FREE(account);
+			 "%s in domain %s - %s\n",
+			 lpcfg_netbios_name(auth_info->lp_ctx),
+			 lpcfg_workgroup(auth_info->lp_ctx),
+			 nt_errstr(status));
 		return false;
 	}
 
-	set_cmdline_auth_info_username(auth_info, account);
-	set_cmdline_auth_info_password(auth_info, pass);
+	return true;
+}
 
-	SAFE_FREE(account);
-	SAFE_FREE(pass);
+static const char *cmdline_auth_info_pw_callback(struct cli_credentials *creds)
+{
+	TALLOC_CTX *frame = talloc_stackframe();
+	const char *name = NULL;
+	char *label = NULL;
+	char *ret = NULL;
+	char pwd[256] = {0};
+	int rc;
 
-	return true;
+	name = cli_credentials_get_unparsed_name(creds, frame);
+	if (name == NULL) {
+		goto fail;
+	}
+	label = talloc_asprintf(frame, "Enter %s's password: ", name);
+	if (label == NULL) {
+		goto fail;
+	}
+	rc = samba_getpass(label, pwd, sizeof(pwd), false, false);
+	if (rc != 0) {
+		goto fail;
+	}
+	ret = talloc_strdup(creds, pwd);
+	if (ret == NULL) {
+		goto fail;
+	}
+	talloc_set_name_const(ret, __location__);
+fail:
+	ZERO_STRUCT(pwd);
+	TALLOC_FREE(frame);
+	return ret;
 }
 
 /****************************************************************************
@@ -322,11 +443,6 @@ bool set_cmdline_auth_info_machine_account_creds(struct user_auth_info *auth_inf
 
 void set_cmdline_auth_info_getpass(struct user_auth_info *auth_info)
 {
-	char *label = NULL;
-	char pwd[256] = {0};
-	int rc;
-	TALLOC_CTX *frame;
-
 	if (get_cmdline_auth_info_got_pass(auth_info) ||
 	    get_cmdline_auth_info_use_ccache(auth_info) ||
 	    get_cmdline_auth_info_use_kerberos(auth_info)) {
@@ -334,12 +450,12 @@ void set_cmdline_auth_info_getpass(struct user_auth_info *auth_info)
 		return;
 	}
 
-	frame = talloc_stackframe();
-	label = talloc_asprintf(frame, "Enter %s's password: ",
-			get_cmdline_auth_info_username(auth_info));
-	rc = samba_getpass(label, pwd, sizeof(pwd), false, false);
-	if (rc == 0) {
-		set_cmdline_auth_info_password(auth_info, pwd);
-	}
-	TALLOC_FREE(frame);
+	cli_credentials_set_password_callback(auth_info->creds,
+					cmdline_auth_info_pw_callback);
+}
+
+struct cli_credentials *get_cmdline_auth_info_creds(
+	const struct user_auth_info *auth_info)
+{
+	return auth_info->creds;
 }
-- 
1.9.1

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: OpenPGP digital signature
URL: <http://lists.samba.org/pipermail/samba-technical/attachments/20161216/ab34d2a4/signature.sig>


More information about the samba-technical mailing list