[PATCH] Various PAM fixes, PAM based password changing.

Andrew Bartlett abartlet at pcug.org.au
Mon Apr 23 15:45:54 GMT 2001


This patch moves the setcred code into a sperate function, adds a delete
credentials to the logout and allows for pam based password changes.

The pam-based password changes should work for the case (not very often
unfortunetly) when we can change the password as the user, ie with the
old password and an external server.  The #if 0ed  code should allow us
to do the normal password changes, but require sombody to get pam
outside passdb, lest they endure linker hell.

The code did work at one stage, but confirmation is eagerly sought as to
password changes from/on various OSs.  It requires a password line in
the /etc/pam.d/samba file.

The rest is tested, as works as far as I can tell.

Andrew Bartlett
abartlet at pcug.org.au

-- 
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/23 14:12:31
@@ -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.34
diff -u -r1.251.2.34 loadparm.c
--- source/param/loadparm.c	2001/04/22 07:20:14	1.251.2.34
+++ source/param/loadparm.c	2001/04/23 14:13:10
@@ -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;
@@ -1003,6 +1010,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},
@@ -1189,6 +1203,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, "");
@@ -1479,6 +1504,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.15
diff -u -r1.1.2.15 pampass.c
--- source/passdb/pampass.c	2001/04/23 06:22:05	1.1.2.15
+++ source/passdb/pampass.c	2001/04/23 14:13:11
@@ -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,38 +424,66 @@
 		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
+
+/* 
+ * PAM Password Changer
  */
-BOOL smb_pam_session(BOOL flag, const char *in_user, char *tty, char *rhost)
+static BOOL smb_pam_chauthtok(pam_handle_t *pamh, char * user, char * oldpassword, char * newpassword)
 {
-	pam_handle_t *pamh = NULL;
-	char * user;
+	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
  */
@@ -382,8 +494,8 @@
 	PAM_username = user;
 	PAM_password = NULL;
 
-	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));
 		}
 	}
@@ -397,14 +509,16 @@
 BOOL smb_pam_passcheck(char * user, char * password)
 {
 	pam_handle_t *pamh = NULL;
-
+	
 	PAM_username = user;
 	PAM_password = password;
-
-	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));
+				}
 			}
 		}
 	}
@@ -412,10 +526,133 @@
 	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;
+
+	/* 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;
+	/* 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, BOOL as_root)
+{
+	pam_handle_t *pamh = NULL;
+
+	uint32 nt_status = NT_STATUS_LOGON_FAILURE;
+
+#if 0
+	if (as_root) {
+		become_root();
+	} else {
+		struct passwd *passwd = sys_getpwnam(user);
+
+		if (passwd == NULL) {
+			return False; 
+		} else {
+			int ruid;
+			ruid = passwd->pw_uid;
+			/* We need to look the same as a set-uid /bin/passwd */
+			save_re_uid();
+			setreuid(ruid, 0);  /* HACK!!!  I just need this to work, there 
+					       probably a much better/more portable way 
+					       to do this */
+			assert_uid(ruid, 0);
+		}
+	}
+#endif
+	
+	if( smb_pam_start(&pamh, user, NULL, &smb_pam_passchange_conversation))
+	{
+		if (smb_pam_chauthtok(pamh, user, oldpassword, newpassword))
+		{
+			if (smb_pam_end(pamh)) {
+				nt_status = NT_STATUS_NOPROBLEMO;
+			} else {
+				nt_status = NT_STATUS_LOGON_FAILURE;
+			}
+			
+		}
+	}
+
+#if 0
+	if (as_root) {
+		unbecome_root();
+	} else {
+		/* We need to look the same as a set-uid /bin/passwd */
+		restore_re_uid();
+	}
+#endif
+
+	if (nt_status != NT_STATUS_NOPROBLEMO)
+		DEBUG(0, ("PAM: Password Change Failed!\n"));
+	return( nt_status == NT_STATUS_NOPROBLEMO);
+}
+
 #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.11
diff -u -r1.1.2.11 srv_netlog_nt.c
--- source/rpc_server/srv_netlog_nt.c	2001/04/23 04:08:29	1.1.2.11
+++ source/rpc_server/srv_netlog_nt.c	2001/04/23 14:13:12
@@ -549,11 +549,9 @@
 	  }
 	} 
 
-#ifdef WITH_PAM
 	if (!smb_pam_accountcheck(nt_username)) {
 	  return NT_STATUS_ACCOUNT_DISABLED;
 	}
-#endif
 
 	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/23 14:13:17
@@ -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,13 @@
 	return (chat_with_program
 		(passwordprogram, name, chatsequence, as_root));
 }
+
+#else /* not WITH_PAM */
+BOOL chgpasswd(char *name, char *oldpass, char *newpass, BOOL as_root)
+{
+	return smb_pam_passchange(name, oldpass, newpass, as_root);
+}
+#endif
 
 #else /* ALLOW_CHANGE_PASSWORD */
 BOOL chgpasswd(char *name, char *oldpass, char *newpass, BOOL as_root)
Index: source/smbd/session.c
===================================================================
RCS file: /cvsroot/samba/source/smbd/session.c,v
retrieving revision 1.1.2.4
diff -u -r1.1.2.4 session.c
--- source/smbd/session.c	2001/04/23 04:08:29	1.1.2.4
+++ source/smbd/session.c	2001/04/23 14:13:31
@@ -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, 
@@ -166,9 +164,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