Hi everyone,
I'm using dropbear v2019.78 and I have a question about the handling of
PAM sessions. From a brief check, it seems that the problem is still
present in v2022.83 but haven't checked on latest source code.

The issue I see is the following: when a connection is created and PAM
is used for authentication, the PAM session handle is not stored. This
makes not possible to listen for PAM session events and so reduces the
possibility to perform auditing like session terminations.

To fix it I have two patches (which apply on 2019.78 but can provide
them also for latest code) which:
   1. when a connection is created and PAM is used for authentication,
      open also a PAM session and store it so when connection
      terminates, we could close it. This way it would be possible to
      listen for PAM session events in order to perform auditing.
   2. During PAM authentication, show user name even if it is not
      valid. This allows to audit for invalid login attempts.

Please let me know if the problem I'm seeing is a misinterpretation of
the code.

Thanks,
Daniele.
From 541f05cbd74017d2d4a8e1251d1d8f9300716b03 Mon Sep 17 00:00:00 2001
From: Daniele Dario <[email protected]>
Date: Fri, 21 Oct 2022 16:47:32 +0200
Subject: [PATCH] handle PAM sessions

When a connection is created, open also a PAM session and when
connection terminates, close it. This way it would be possible to listen
for PAM session events in order to perform auditing.

Signed-off-by: Daniele Dario <[email protected]>
---
 auth.h           |   1 +
 common-session.c |  32 ++++++++++++
 session.h        |   4 ++
 svr-auth.c       |   1 +
 svr-authpam.c    | 124 +++++++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 159 insertions(+), 3 deletions(-)

diff --git a/auth.h b/auth.h
index 4bdb359..a9e9b2d 100644
--- a/auth.h
+++ b/auth.h
@@ -40,6 +40,7 @@ void send_msg_userauth_banner(const buffer *msg);
 void svr_auth_password(int valid_user);
 void svr_auth_pubkey(int valid_user);
 void svr_auth_pam(int valid_user);
+void svr_blankpass_pam(void);
 
 #if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
 int svr_pubkey_allows_agentfwd(void);
diff --git a/common-session.c b/common-session.c
index aa31e49..a19f953 100644
--- a/common-session.c
+++ b/common-session.c
@@ -36,6 +36,14 @@
 #include "runopts.h"
 #include "netio.h"
 
+#if DROPBEAR_SVR_PAM_AUTH
+# if defined (HAVE_SECURITY_PAM_APPL_H)
+#  include <security/pam_appl.h>
+# elif defined (HAVE_PAM_PAM_APPL_H)
+#  include <pam/pam_appl.h>
+# endif
+#endif /* DROPBEAR_SVR_PAM_AUTH */
+
 static void checktimeouts(void);
 static long select_timeout(void);
 static int ident_readln(int fd, char* buf, int count);
@@ -147,6 +155,10 @@ void common_session_init(int sock_in, int sock_out) {
 
 	ses.allowprivport = 0;
 
+#if DROPBEAR_SVR_PAM_AUTH
+	ses.pamh = NULL;
+#endif /* DROPBEAR_SVR_PAM_AUTH */
+
 	TRACE(("leave session_init"))
 }
 
@@ -289,6 +301,8 @@ static void cleanup_buf(buffer **buf) {
 /* clean up a session on exit */
 void session_cleanup() {
 	
+	int rc;
+
 	TRACE(("enter session_cleanup"))
 	
 	/* we can't cleanup if we don't know the session state */
@@ -351,6 +365,24 @@ void session_cleanup() {
 	m_burn(ses.keys, sizeof(struct key_context));
 	m_free(ses.keys);
 
+#if DROPBEAR_SVR_PAM_AUTH
+	if (ses.pamh != NULL) {
+		/* Close PAM session */
+		rc = pam_close_session (ses.pamh, 0);
+		if (rc != PAM_SUCCESS) {
+			dropbear_log(LOG_WARNING, "pam_close_session() failed, rc=%d, %s",
+					rc, pam_strerror(ses.pamh, rc));
+		} else {
+			dropbear_log(LOG_NOTICE, "PAM session closed for '%s' from %s",
+					ses.authstate.pw_name, svr_ses.addrstring);
+		}
+
+		/* Release PAM handle */
+		pam_end(ses.pamh, rc);
+		ses.pamh = NULL;
+	}
+#endif /* DROPBEAR_SVR_PAM_AUTH */
+
 	TRACE(("leave session_cleanup"))
 }
 
diff --git a/session.h b/session.h
index 0f77055..a08b0f2 100644
--- a/session.h
+++ b/session.h
@@ -192,6 +192,10 @@ struct sshsession {
 	void(*extra_session_cleanup)(void); /* client or server specific cleanup */
 	void(*send_kex_first_guess)(void);
 
+#if DROPBEAR_SVR_PAM_AUTH
+	void *pamh; /* Pointer to PAM handle */
+#endif /* DROPBEAR_SVR_PAM_AUTH */
+
 	struct AuthState authstate; /* Common amongst client and server, since most
 								   struct elements are common */
 
diff --git a/svr-auth.c b/svr-auth.c
index 7575f90..9a3bc67 100644
--- a/svr-auth.c
+++ b/svr-auth.c
@@ -134,6 +134,7 @@ void recv_msg_userauth_request() {
 					"Auth succeeded with blank password for '%s' from %s",
 					ses.authstate.pw_name,
 					svr_ses.addrstring);
+			svr_blankpass_pam();
 			send_msg_userauth_success();
 			goto out;
 		}
diff --git a/svr-authpam.c b/svr-authpam.c
index 30f0b03..a38fc52 100644
--- a/svr-authpam.c
+++ b/svr-authpam.c
@@ -187,7 +187,8 @@ void svr_auth_pam(int valid_user) {
 	};
 	const char* printable_user = NULL;
 
-	pam_handle_t* pamHandlep = NULL;
+	pam_handle_t* pamHandlep = ses.pamh;
+	unsigned char pamSessionReady = 0;
 
 	char * password = NULL;
 	unsigned int passwordlen;
@@ -222,6 +223,14 @@ void svr_auth_pam(int valid_user) {
 		printable_user = "<invalid username>";
 	}
 
+	if (pamHandlep != NULL) {
+	  /* Re-auth? */
+	  dropbear_log(LOG_NOTICE, "Re-authentication attempt for '%s' from %s",
+	      printable_user, svr_ses.addrstring);
+	  pamSessionReady = 1;
+	  goto pamdone;
+	}
+
 	/* Init pam */
 	if ((rc = pam_start("dropbear", NULL, &pamConv, &pamHandlep)) != PAM_SUCCESS) {
 		dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", 
@@ -271,6 +280,19 @@ void svr_auth_pam(int valid_user) {
 		goto cleanup;
 	}
 
+	/* Open PAM session */
+	if ((rc = pam_open_session(pamHandlep, 0)) != PAM_SUCCESS) {
+		dropbear_log(LOG_NOTICE, "pam_open_session() failed, rc=%d, %s",
+				rc, pam_strerror(pamHandlep, rc));
+		dropbear_log(LOG_NOTICE,
+				"Failed to open PAM session for '%s' from %s",
+				printable_user,
+				svr_ses.addrstring);
+	} else {
+		pamSessionReady = 1;
+	}
+
+pamdone:
 	if (!valid_user) {
 		/* PAM auth succeeded but the username isn't allowed in for another reason
 		(checkusername() failed) */
@@ -290,8 +312,104 @@ cleanup:
 		m_free(password);
 	}
 	if (pamHandlep != NULL) {
-		TRACE(("pam_end"))
-		(void) pam_end(pamHandlep, 0 /* pam_status */);
+		if (rc != PAM_SUCCESS) {
+			/* Something went wrong during PAM authentication:
+			 * clean PAM handle */
+			if (pamSessionReady) {
+				/* Need to close PAM session before to cleanup */
+				pam_close_session(pamHandlep, 0);
+			}
+			pam_end(pamHandlep, rc);
+			pamHandlep = NULL;
+		}
+
+		/* Store PAM handle in session. If it was valid we'd use it
+		 * to close the PAM session on session termination. Else,
+		 * we'd know that it's not needed to do it */
+		ses.pamh = pamHandlep;
+	}
+}
+
+/* If a valid user has auth method set to "none" and blank password is allowed,
+ * open a PAM session for him. */
+void svr_blankpass_pam(void) {
+
+	/* This is needed to init PAM but in theory unused */
+	struct UserDataS userData = {NULL, NULL};
+	struct pam_conv pamConv = {
+		pamConvFunc,
+		&userData /* submitted to pamvConvFunc as appdata_ptr */
+	};
+
+	int rc = PAM_SUCCESS;
+	unsigned char pamSessionReady = 0;
+	pam_handle_t* pamHandlep = ses.pamh;
+
+	if (pamHandlep != NULL) {
+		/* Re-auth? */
+		dropbear_log(LOG_NOTICE, "Re-authentication attempt for '%s' from %s",
+				ses.authstate.pw_name, svr_ses.addrstring);
+		pamSessionReady = 1;
+		goto cleanup;
+	}
+
+	/* Init pam */
+	if ((rc = pam_start("dropbear", ses.authstate.pw_name, &pamConv, &pamHandlep)) != PAM_SUCCESS) {
+		dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s",
+				rc, pam_strerror(pamHandlep, rc));
+		goto cleanup;
+	}
+
+	/* just to set it to something */
+	if ((rc = pam_set_item(pamHandlep, PAM_TTY, "ssh")) != PAM_SUCCESS) {
+		dropbear_log(LOG_WARNING, "pam_set_item() failed, rc=%d, %s",
+				rc, pam_strerror(pamHandlep, rc));
+		goto cleanup;
+	}
+
+	if ((rc = pam_set_item(pamHandlep, PAM_RHOST, svr_ses.remotehost)) != PAM_SUCCESS) {
+		dropbear_log(LOG_WARNING, "pam_set_item() failed, rc=%d, %s",
+				rc, pam_strerror(pamHandlep, rc));
+		goto cleanup;
+	}
+
+#ifdef HAVE_PAM_FAIL_DELAY
+	/* We have our own random delay code already, disable PAM's */
+	(void) pam_fail_delay(pamHandlep, 0 /* musec_delay */);
+#endif
+
+	/* (void) pam_set_item(pamHandlep, PAM_FAIL_DELAY, (void*) pamDelayFunc); */
+
+	/* Open PAM session */
+	if ((rc = pam_open_session(pamHandlep, 0)) != PAM_SUCCESS) {
+		dropbear_log(LOG_NOTICE, "pam_open_session() failed, rc=%d, %s",
+				rc, pam_strerror(pamHandlep, rc));
+		dropbear_log(LOG_NOTICE, "Failed to open PAM session for '%s' from %s",
+				ses.authstate.pw_name,
+				svr_ses.addrstring);
+	} else {
+		pamSessionReady = 1;
+	}
+
+cleanup:
+	if (pamHandlep != NULL) {
+		if (rc != PAM_SUCCESS) {
+			/* Something went wrong during PAM open session:
+			 * clean PAM handle */
+
+			if (pamSessionReady) {
+				/* Need to close PAM session before to cleanup */
+				pam_close_session(pamHandlep, 0);
+			}
+
+			pam_end(pamHandlep, rc);
+			pamHandlep = NULL;
+		}
+
+		/* Store PAM handle in session. If it was valid we'd use it
+		 * to close the PAM session on session termination. Else,
+		 * we'd know that it's not needed to do it */
+		ses.pamh = pamHandlep;
 	}
 }
 
-- 
2.38.1

From a0272594f72204ec4be92010cb10db2d98a87be5 Mon Sep 17 00:00:00 2001
From: Daniele Dario <[email protected]>
Date: Mon, 24 Oct 2022 11:08:33 +0200
Subject: [PATCH] show user during auth

During PAM auth, show user name even if it is not valid. This allows to
audit for invalid login attempts.

Signed-off-by: Daniele Dario <[email protected]>
---
 svr-authpam.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/svr-authpam.c b/svr-authpam.c
index a38fc52..1cad7ba 100644
--- a/svr-authpam.c
+++ b/svr-authpam.c
@@ -219,6 +219,8 @@ void svr_auth_pam(int valid_user) {
 
 	if (ses.authstate.pw_name) {
 		printable_user = ses.authstate.pw_name;
+	} else if (ses.authstate.username) {
+		printable_user = ses.authstate.username;
 	} else {
 		printable_user = "<invalid username>";
 	}
@@ -232,7 +234,7 @@ void svr_auth_pam(int valid_user) {
 	}
 
 	/* Init pam */
-	if ((rc = pam_start("dropbear", NULL, &pamConv, &pamHandlep)) != PAM_SUCCESS) {
+	if ((rc = pam_start("dropbear", printable_user, &pamConv, &pamHandlep)) != PAM_SUCCESS) {
 		dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", 
 				rc, pam_strerror(pamHandlep, rc));
 		goto cleanup;
-- 
2.38.1

Reply via email to