[PATCH] Re: W2K Domain Login Problem with 2.2.0

Andrew Bartlett abartlet at pcug.org.au
Tue Apr 24 04:11:09 GMT 2001


Steve Langasek wrote:
> 
> On Tue, 24 Apr 2001, Andrew Bartlett wrote:
> 
> > Finally, an explaination that MAKES SENSE.  Whats more, the reason it
> > worked for me is that I use NIS, so didn't need to be root.
> 
> It also explains why PAM fails for some operations and not for others; owing
> to RPC, Samba isn't always calling PAM from the same security context.  Still,
> I'd like some empirical confirmation that my patch fixes the problem before
> concluding that this is what's really happening.
> 
> Steve Langasek
> postmodern programmer

The attached patch fixes the become_root()/unbecome_root() needed for
account checking, and adds password changing via PAM.  It moves
pam_setcred into a seperate function, and deletes credentials on close. 
I think it knocks out all the known issues with PAM at the moment, bar
the chaining of pamh handles from pam use to pam use.  It also doesn't
stuff up our linking with the various other utilities in PAM.  I would
be VERY supprised if this doesn't fix it - as its ONLY nt domain stuff
thats called from nobody, and this bugs victums report being able to
mount the shares correctly - where there is also a pam account check.

Andrew Bartlett

-- 
Andrew Bartlett
abartlet at pcug.org.au
-------------- next part --------------
Index: source/include/local.h
===================================================================
RCS file: /cvsroot/samba/source/include/local.h,v
retrieving revision 1.54.4.1
diff -u -r1.54.4.1 local.h
--- source/include/local.h	2001/04/18 16:30:28	1.54.4.1
+++ source/include/local.h	2001/04/24 03:04:23
@@ -173,6 +173,14 @@
 
 #define DEFAULT_PASSWD_CHAT "*new*password* %n\\n *new*password* %n\\n *changed*"
 
+/*
+ * Default passwd chat script.
+ */
+
+#define DEFAULT_PAM_CURRENTPW_PROMPT "(current)"
+#define DEFAULT_PAM_NEWPW_PROMPT "New"
+#define DEFAULT_PAM_REPPW_PROMPT "Retype"
+
 /* Minimum length of allowed password when changing UNIX password. */
 #define MINPASSWDLENGTH 5
 
Index: source/param/loadparm.c
===================================================================
RCS file: /cvsroot/samba/source/param/loadparm.c,v
retrieving revision 1.251.2.35
diff -u -r1.251.2.35 loadparm.c
--- source/param/loadparm.c	2001/04/23 20:43:24	1.251.2.35
+++ source/param/loadparm.c	2001/04/24 03:05:01
@@ -180,6 +180,11 @@
 	char *szAddShareCommand;
 	char *szChangeShareCommand;
 	char *szDeleteShareCommand;
+#ifdef WITH_PAM
+	char *szPAMcurrentpw;
+	char *szPAMnewpw;
+	char *szPAMreppw;
+#endif
 	int max_log_size;
 	int mangled_stack;
 	int max_xmit;
@@ -1005,6 +1012,13 @@
 	{"hide local users", P_BOOL, P_GLOBAL, &Globals.bHideLocalUsers, NULL,
 	 NULL, 0},
 
+#ifdef WITH_PAM
+	{"PAM options", P_SEP, P_SEPARATOR},
+	
+	{"pam currentpw prompt", P_STRING, P_GLOBAL, &Globals.szPAMcurrentpw, NULL, NULL, FLAG_GLOBAL},
+	{"pam newpw prompt", P_STRING, P_GLOBAL, &Globals.szPAMnewpw, NULL, NULL, FLAG_GLOBAL},
+	{"pam repeatpw prompt", P_STRING, P_GLOBAL, &Globals.szPAMreppw, NULL, NULL, FLAG_GLOBAL},
+#endif
 	{"VFS options", P_SEP, P_SEPARATOR},
 	
 	{"vfs object", P_STRING, P_LOCAL, &sDefault.szVfsObjectFile, handle_vfs_object, NULL, 0},
@@ -1191,6 +1205,16 @@
 	string_set(&Globals.szPasswdProgram, PASSWD_PROGRAM);
 	string_set(&Globals.szPrintcapname, PRINTCAP_NAME);
 	string_set(&Globals.szLockDir, LOCKDIR);
+
+	/*
+	 * Allow the default PAM password change chat to be overridden in local.h.
+	 */
+#ifdef WITH_PAM
+	string_set(&Globals.szPAMcurrentpw, DEFAULT_PAM_CURRENTPW_PROMPT);
+	string_set(&Globals.szPAMnewpw, DEFAULT_PAM_NEWPW_PROMPT);
+	string_set(&Globals.szPAMreppw, DEFAULT_PAM_REPPW_PROMPT);
+#endif
+
 #ifdef WITH_UTMP
 	string_set(&Globals.szUtmpDir, "");
 	string_set(&Globals.szWtmpDir, "");
@@ -1482,6 +1507,11 @@
 FN_GLOBAL_STRING(lp_add_share_cmd, &Globals.szAddShareCommand)
 FN_GLOBAL_STRING(lp_change_share_cmd, &Globals.szChangeShareCommand)
 FN_GLOBAL_STRING(lp_delete_share_cmd, &Globals.szDeleteShareCommand)
+#ifdef WITH_PAM
+FN_GLOBAL_STRING(lp_pam_currentpw_prompt, &Globals.szPAMcurrentpw)
+FN_GLOBAL_STRING(lp_pam_newpw_prompt, &Globals.szPAMnewpw)
+FN_GLOBAL_STRING(lp_pam_repeatpw_prompt, &Globals.szPAMreppw)
+#endif /* WITH_PAM */
 
 #ifdef WITH_SSL
 FN_GLOBAL_INTEGER(lp_ssl_version, &Globals.sslVersion)
Index: source/passdb/pampass.c
===================================================================
RCS file: /cvsroot/samba/source/passdb/pampass.c,v
retrieving revision 1.1.2.16
diff -u -r1.1.2.16 pampass.c
--- source/passdb/pampass.c	2001/04/23 20:43:24	1.1.2.16
+++ source/passdb/pampass.c	2001/04/24 03:05:02
@@ -50,6 +53,7 @@
 
 static char *PAM_username;
 static char *PAM_password;
+static char *PAM_newpassword;
 
 /*
  *  Macros to help make life easy
@@ -128,6 +132,79 @@
 	NULL
 };
 
+/*
+ * PAM conversation function
+ * Here we assume (for now, at least) that echo on means login name, and
+ * echo off means password.
+ */
+
+static int smb_pam_passchange_conv(int num_msg,
+		    const struct pam_message **msg,
+		    struct pam_response **resp,
+		    void *appdata_ptr)
+{
+	int replies = 0;
+	struct pam_response *reply = NULL;
+
+	reply = malloc(sizeof(struct pam_response) * num_msg);
+	if (!reply)
+		return PAM_CONV_ERR;
+
+	for (replies = 0; replies < num_msg; replies++)
+	{
+		switch (msg[replies]->msg_style)
+		{
+			case PAM_PROMPT_ECHO_ON:
+				reply[replies].resp_retcode = PAM_SUCCESS;
+			reply[replies].resp =
+					COPY_STRING(PAM_username);
+				/* PAM frees resp */
+				break;
+
+			case PAM_PROMPT_ECHO_OFF:
+				reply[replies].resp_retcode = PAM_SUCCESS;
+				DEBUG(10,("PAM Replied: %s\n", msg[replies]->msg));
+				if (strncmp(lp_pam_currentpw_prompt(), msg[replies]->msg, strlen(lp_pam_currentpw_prompt())) == 0) {
+					reply[replies].resp =
+						COPY_STRING(PAM_password);
+				} else if (strncmp(lp_pam_newpw_prompt(), msg[replies]->msg, strlen(lp_pam_newpw_prompt())) == 0) {
+					reply[replies].resp =
+						COPY_STRING(PAM_newpassword);
+				} else if (strncmp(lp_pam_repeatpw_prompt(), msg[replies]->msg, strlen(lp_pam_repeatpw_prompt())) == 0) {
+					reply[replies].resp =
+						COPY_STRING(PAM_newpassword);
+				} else {
+					DEBUG(3,("Could not find reply for PAM prompt: %s\n",msg[replies]->msg));
+					DEBUG(5,("Prompts available:\n CurrentPW: \"%s\"\n NewPW: \"%s\"\n RepeatPW: \"%s\"\n",lp_pam_currentpw_prompt(),lp_pam_newpw_prompt(),lp_pam_repeatpw_prompt()));
+				}
+				/* PAM frees resp */
+				break;
+
+			case PAM_TEXT_INFO:
+				/* fall through */
+
+			case PAM_ERROR_MSG:
+				/* ignore it... */
+				reply[replies].resp_retcode = PAM_SUCCESS;
+				reply[replies].resp = NULL;
+				break;
+
+			default:
+				/* Must be an error of some sort... */
+				free(reply);
+				return PAM_CONV_ERR;
+		}
+	}
+	if (reply)
+		*resp = reply;
+	return PAM_SUCCESS;
+}
+
+static struct pam_conv smb_pam_passchange_conversation = {
+	&smb_pam_passchange_conv,
+	NULL
+};
+
 /* 
  * PAM Closing out cleanup handler
  */
@@ -149,15 +226,19 @@
 /*
  * Start PAM authentication for specified account
  */
-static BOOL smb_pam_start(pam_handle_t **pamh, char *user, char *rhost)
+static BOOL smb_pam_start(pam_handle_t **pamh, char *user, char *rhost, struct pam_conv * selected_pam_conv)
 {
 	int pam_error;
 
 	*pamh = (pam_handle_t *)NULL;
 
 	DEBUG(4,("PAM: Init user: %s\n", user));
+
+	if (selected_pam_conv == NULL) {
+		selected_pam_conv = &smb_pam_conversation;
+	}
 
-	pam_error = pam_start("samba", user, &smb_pam_conversation, pamh);
+	pam_error = pam_start("samba", user, selected_pam_conv, pamh);
 	if( !smb_pam_error_handler(*pamh, pam_error, "Init Failed", 0)) {
 		*pamh = (pam_handle_t *)NULL;
 		return False;
@@ -228,7 +309,7 @@
 			DEBUG(4, ("PAM: User %s Authenticated OK\n", user));
 		        break;
 		default:
-			DEBUG(0, ("PAM: UNKNOWN ERROR while authenticating user %s\n", user));
+			DEBUG(0, ("PAM: UNKNOWN ERROR (%d) while authenticating user %s\n", pam_error, user));
 	}
 	if(!smb_pam_error_handler(pamh, pam_error, "Authentication Failure", 2)) {
 		smb_pam_end(pamh);
@@ -241,7 +322,7 @@
 /* 
  * PAM Account Handler
  */
-static BOOL smb_pam_account(pam_handle_t *pamh, char * user, char * password, BOOL pam_auth)
+static BOOL smb_pam_account(pam_handle_t *pamh, char * user, char * password)
 {
 	int pam_error;
 
@@ -267,20 +348,23 @@
 			DEBUG(4, ("PAM: Account OK for User: %s\n", user));
 		        break;
 		default:
-			DEBUG(0, ("PAM: UNKNOWN ERROR for User: %s\n", user));
+			DEBUG(0, ("PAM: UNKNOWN PAM ERROR (%d) during Account Management for User: %s\n", pam_error, user));
 	}
 	if(!smb_pam_error_handler(pamh, pam_error, "Account Check Failed", 2)) {
 		smb_pam_end(pamh);
 		return False;
 	}
+	
+	/* If this point is reached, the user has been authenticated. */
+	return (True);
+}
 
-	/* Skip the pam_setcred() call if we didn't use pam_authenticate()
-	   for authentication -- it's an error to call pam_setcred without
-	   calling pam_authenticate first */
-	if (!pam_auth) {
-		DEBUG(4, ("PAM: Skipping setcred for user: %s (using encrypted passwords)\n", user));
-		return True;
-	}
+/* 
+ * PAM Credential Setting
+ */
+static BOOL smb_pam_setcred(pam_handle_t *pamh, char * user)
+{
+	int pam_error;
 
 	/*
 	 * This will allow samba to aquire a kerberos token. And, when
@@ -306,7 +390,7 @@
 			DEBUG(4, ("PAM: SetCredentials OK for User: %s\n", user));
 		        break;
 		default:
-			DEBUG(0, ("PAM: Error Condition Unknown in pam_setcred function call!"));
+			DEBUG(0, ("PAM: UNKNOWN PAM ERROR (%d) during SetCredentials for User: %s\n", pam_error, user));
 	}
 	if(!smb_pam_error_handler(pamh, pam_error, "Set Credential Failure", 2)) {
 		smb_pam_end(pamh);
@@ -321,7 +405,7 @@
 /*
  * PAM Internal Session Handler
  */
-static BOOL smb_internal_pam_session(pam_handle_t *pamh, char *user, char *tty, BOOL flag)
+static BOOL smb_internal_pam_session(pam_handle_t *pamh, char *user, const char *tty, BOOL flag)
 {
 	int pam_error;
 
@@ -340,44 +424,67 @@
 		if (!smb_pam_error_handler(pamh, pam_error, "session setup failed", 0))
 			return False;
 	} else {
-		pam_error = pam_close_session(pamh, PAM_SILENT);
+		pam_setcred(pamh, (PAM_DELETE_CRED|PAM_SILENT));  /* We don't care if this fails */ 
+		pam_error = pam_close_session(pamh, PAM_SILENT);  /* This will probably pick up the error anyway */
 		if (!smb_pam_error_handler(pamh, pam_error, "session close failed", 0))
 			return False;
 	}
 	return (True);
 }
 
-/*
- * PAM Externally accessible Session handler
- */
 
-BOOL smb_pam_session(BOOL flag, const char *in_user, char *tty, char *rhost)
+/* 
+ * PAM Password Changer
+ */
+static BOOL smb_pam_chauthtok(pam_handle_t *pamh, char * user, char * oldpassword, char * newpassword)
 {
-	pam_handle_t *pamh = NULL;
-	char * user;
-
-	/* Ignore PAM if told to. */
-
-	if (!lp_obey_pam_restrictions())
-		return True;
+	int pam_error;
 
-	user = strdup(in_user);
-	if ( user == NULL ) {
-		DEBUG(0, ("PAM: PAM_session Malloc Failed!\n"));
-		return False;
-	}
+	PAM_username = user;
+	PAM_password = oldpassword;
+	PAM_newpassword = newpassword;
 
-	if (!smb_pam_start(&pamh, user, rhost)) {
-		return False;
+        DEBUG(4,("PAM: Password Change for User: %s\n", user));
+	pam_error = pam_chauthtok(pamh, PAM_SILENT); /* Change Password */
+	switch( pam_error ) {
+	case PAM_AUTHTOK_ERR:
+		DEBUG(2, ("PAM: unable to obtain the new authentication token - is password to weak?\n"));
+		break;
+	case PAM_AUTHTOK_RECOVER_ERR:
+		DEBUG(2, ("PAM: unable to obtain the old authentication token - was the old password wrong?.\n"));
+		break;
+	case PAM_AUTHTOK_LOCK_BUSY:
+		DEBUG(2, ("PAM: unable to change the authentication token since it is currently locked.\n"));
+		break;
+	case PAM_AUTHTOK_DISABLE_AGING:
+		DEBUG(2, ("PAM: Authentication token aging has been disabled.\n"));
+		break;
+	case PAM_PERM_DENIED:
+		DEBUG(0, ("PAM: Permission denied.\n"));
+		break;
+	case PAM_TRY_AGAIN:
+		DEBUG(0, ("PAM: Could not update all authentication token(s). No authentication tokens were updated.\n"));
+		break;
+	case PAM_USER_UNKNOWN:
+		DEBUG(0, ("PAM: User not known to PAM\n"));
+		break;
+	case PAM_SUCCESS:
+		DEBUG(4, ("PAM: Account OK for User: %s\n", user));
+		break;
+	default:
+		DEBUG(0, ("PAM: UNKNOWN PAM ERROR (%d) for User: %s\n", pam_error, user));
 	}
 
-	if (!smb_internal_pam_session(pamh, user, tty, flag)) {
+	if(!smb_pam_error_handler(pamh, pam_error, "Password Change Failed", 2)) {
 		smb_pam_end(pamh);
 		return False;
 	}
-	return smb_pam_end(pamh);
+	
+	/* If this point is reached, the password has changed. */
+	return True;
 }
 
+
 /*
  * PAM Externally accessible Account handler
  */
@@ -393,8 +500,8 @@
 	if (!lp_obey_pam_restrictions())
 		return True;
 
-	if( smb_pam_start(&pamh, user, NULL)) {
-		if ( smb_pam_account(pamh, user, NULL, False)) {
+	if( smb_pam_start(&pamh, user, NULL, NULL)) {
+		if ( smb_pam_account(pamh, user, NULL)) {
 			return( smb_pam_end(pamh));
 		}
 	}
@@ -408,7 +515,7 @@
 BOOL smb_pam_passcheck(char * user, char * password)
 {
 	pam_handle_t *pamh = NULL;
-
+	
 	PAM_username = user;
 	PAM_password = password;
 
@@ -418,10 +525,12 @@
 	 * compiled --with-pam.
 	 */
 
-	if( smb_pam_start(&pamh, user, NULL)) {
+	if( smb_pam_start(&pamh, user, NULL, NULL)) {
 		if ( smb_pam_auth(pamh, user, password)) {
-			if ( smb_pam_account(pamh, user, password, True)) {
-				return( smb_pam_end(pamh));
+			if ( smb_pam_account(pamh, user, password)) {
+				if ( smb_pam_setcred(pamh, user)) {
+					return( smb_pam_end(pamh));
+				}
 			}
 		}
 	}
@@ -429,10 +538,111 @@
 	return( False );
 }
 
+/*
+ * PAM Externally accessible Session handler
+ */
+BOOL smb_pam_claim_session(const char *in_user, char *tty, char *rhost)
+{
+	pam_handle_t *pamh = NULL;
+	char * user;
+
+	/* Ignore PAM if told to. */
+
+	if (!lp_obey_pam_restrictions())
+		return True;
+
+	/* This is freed by PAM */
+	user = strdup(in_user);
+
+	if ( user == NULL ) {
+                DEBUG(0, ("PAM: PAM_session Malloc Failed!\n"));
+                return False;
+        }
+
+	if (!smb_pam_start(&pamh, user, rhost, NULL)) {
+		smb_pam_end(pamh);
+		return False;
+	}
+
+	if (smb_internal_pam_session(pamh, user, tty, True)) {
+		return smb_pam_end(pamh);
+	} else {
+		smb_pam_end(pamh);
+		return False;
+	}
+}
+
+/*
+ * PAM Externally accessible Session handler
+ */
+BOOL smb_pam_close_session(char *in_user, char *tty, char *rhost)
+{
+	pam_handle_t *pamh = NULL;
+
+	char * user;
+	/* Ignore PAM if told to. */
+
+	if (!lp_obey_pam_restrictions())
+		return True;
+
+	/* This is freed by PAM */
+	user = strdup(in_user);
+	
+	if (!smb_pam_start(&pamh, user, rhost, NULL)) {
+		smb_pam_end(pamh);
+		return False;
+	}
+
+	
+
+	if (smb_internal_pam_session(pamh, user, tty, False)) {
+		return smb_pam_end(pamh);
+	} else {
+		smb_pam_end(pamh);
+		return False;
+	}
+}
+
+/*
+ * PAM Password Change Suite
+ */
+BOOL smb_pam_passchange(char * user, char * oldpassword, char * newpassword)
+{
+
+	/* Appropriate quantities of root should be obtained BEFORE calling this funcation */
+
+	pam_handle_t *pamh = NULL;
+
+	if( smb_pam_start(&pamh, user, NULL, &smb_pam_passchange_conversation))
+	{
+		if (smb_pam_chauthtok(pamh, user, oldpassword, newpassword))
+		{
+			if (smb_pam_end(pamh)) {
+				return True;
+			}
+			
+		}
+	}
+
+	DEBUG(0, ("PAM: Password Change Failed!\n"));
+	return( False );
+}
+
 #else
 
 /* If PAM not used, no PAM restrictions on accounts. */
  BOOL smb_pam_accountcheck(char * user)
+{
+	return True;
+}
+
+/* If PAM not used, also no PAM restrictions on sessions. */
+ BOOL smb_pam_claim_session(const char *in_user, char *tty, char *rhost)
+{
+	return True;
+}
+
+ BOOL smb_pam_close_session(const char *in_user, char *tty, char *rhost)
 {
 	return True;
 }
Index: source/rpc_server/srv_netlog_nt.c
===================================================================
RCS file: /cvsroot/samba/source/rpc_server/srv_netlog_nt.c,v
retrieving revision 1.1.2.12
diff -u -r1.1.2.12 srv_netlog_nt.c
--- source/rpc_server/srv_netlog_nt.c	2001/04/23 23:31:00	1.1.2.12
+++ source/rpc_server/srv_netlog_nt.c	2001/04/24 03:05:04
@@ -593,11 +593,12 @@
 	  }
 	} 
 
-#ifdef WITH_PAM
+	become_root();
 	if (!smb_pam_accountcheck(nt_username)) {
-	  return NT_STATUS_ACCOUNT_DISABLED;
+		unbecome_root();
+		return NT_STATUS_ACCOUNT_DISABLED;
 	}
-#endif
+	unbecome_root();
 
 	if (!(smb_pass->acct_ctrl & ACB_PWNOTREQ)) {
 		switch (q_u->sam_id.logon_level) {
Index: source/smbd/chgpasswd.c
===================================================================
RCS file: /cvsroot/samba/source/smbd/chgpasswd.c,v
retrieving revision 1.64.4.1
diff -u -r1.64.4.1 chgpasswd.c
--- source/smbd/chgpasswd.c	2001/02/21 00:11:55	1.64.4.1
+++ source/smbd/chgpasswd.c	2001/04/24 03:05:09
@@ -52,6 +52,7 @@
 extern int DEBUGLEVEL;
 
 #if ALLOW_CHANGE_PASSWORD
+#ifndef WITH_PAM /* We have MUCH better ways of doing this... */
 
 static int findpty(char **slave)
 {
@@ -525,6 +526,19 @@
 	return (chat_with_program
 		(passwordprogram, name, chatsequence, as_root));
 }
+
+#else /* not WITH_PAM */
+BOOL chgpasswd(char *name, char *oldpass, char *newpass, BOOL as_root)
+{
+	if (as_root) {
+		become_root();
+	}
+	return smb_pam_passchange(name, oldpass, newpass);
+	if (as_root) {
+		unbecome_root();
+	}
+}
+#endif
 
 #else /* ALLOW_CHANGE_PASSWORD */
 BOOL chgpasswd(char *name, char *oldpass, char *newpass, BOOL as_root)
Index: source/smbd/password.c
===================================================================
RCS file: /cvsroot/samba/source/smbd/password.c,v
retrieving revision 1.186.2.19
diff -u -r1.186.2.19 password.c
--- source/smbd/password.c	2001/04/23 04:08:29	1.186.2.19
+++ source/smbd/password.c	2001/04/24 03:05:14
Index: source/smbd/session.c
===================================================================
RCS file: /cvsroot/samba/source/smbd/session.c,v
retrieving revision 1.1.2.5
diff -u -r1.1.2.5 session.c
--- source/smbd/session.c	2001/04/23 23:07:53	1.1.2.5
+++ source/smbd/session.c	2001/04/24 03:05:24
@@ -99,6 +99,13 @@
 	sessionid.id_num = i;
 	sessionid.pid = pid;
 
+	if (!smb_pam_claim_session(sessionid.username, sessionid.id_str, sessionid.hostname)) {
+		DEBUG(1,("pam_session rejected the session for %s [%s]\n",
+			 sessionid.username, sessionid.id_str));
+		tdb_delete(tdb, key);
+		return False;
+	}
+
 	dlen = tdb_pack(dbuf, sizeof(dbuf), "fffdd",
 			sessionid.username, sessionid.hostname, sessionid.id_str,
 			sessionid.id_num, sessionid.pid);
@@ -110,15 +117,6 @@
 		return False;
 	}
 
-#if WITH_PAM
-	if (!smb_pam_session(True, sessionid.username, sessionid.id_str, sessionid.hostname)) {
-		DEBUG(1,("smb_pam_session rejected the session for %s [%s]\n",
-			 sessionid.username, sessionid.id_str));
-		tdb_delete(tdb, key);
-		return False;
-	}
-#endif
-
 #if WITH_UTMP	
 	if (lp_utmp()) {
 		sys_utmp_claim(sessionid.username, sessionid.hostname, 
@@ -169,9 +167,7 @@
 	}
 #endif
 
-#if WITH_PAM
-	smb_pam_session(False, sessionid.username, sessionid.id_str, sessionid.hostname);
-#endif
+	smb_pam_close_session(sessionid.username, sessionid.id_str, sessionid.hostname);
 
 	tdb_delete(tdb, key);
 }


More information about the samba-technical mailing list