PATCH - add username_format to the PAM auth module

Bradley Kite
Fri, 12 Dec 2014 11:35:01 -0000

Hi there,

Other auth modules (eg passwd-file) allow a username_format to be
specified, but not the PAM module.

The use-case, is where I want a static userdb configuration which takes the
domain into account but still want to use PAM for authentication, eg:

userdb {
  driver = static
  args = uid=8 gid=12 home=/mnt/storage/mail/vhosts/%d/%n
}

passdb {
  driver = pam
  args = username_format=%n allow_pam_transform=no dovecot
}

The global "auth_username_format" setting ends up changing the
username, so
looses the ability to have different mailboxes based on domain.

There is also a new setting, allow_pam_transform which stops the username
being changed after a successful authentication. Normally, if PAM changes
the username, then dovecot must update its record of the username for
further processing - but in this use case we must disable this function.

--
Brad.
-------------- next part --------------
--- dovecot-2.2.10/src/auth/passdb-pam.c.orig	2014-12-11 22:48:47.861478049
+0000
+++ dovecot-2.2.10/src/auth/passdb-pam.c	2014-12-12 11:25:23.304742138 +0000
@@ -41,12 +41,13 @@
 struct pam_passdb_module {
 	struct passdb_module module;

-	const char *service_name, *pam_cache_key;
+	const char *service_name, *pam_cache_key, *username_format;
 	unsigned int requests_left;

 	unsigned int pam_setcred:1;
 	unsigned int pam_session:1;
 	unsigned int failure_show_msg:1;
+	unsigned int pam_allow_transform:1;
 };

 struct pam_conv_context {
@@ -67,6 +68,13 @@
 	char *string;
 	int i;

+	const struct var_expand_table *table;
+	string_t *username;
+
+	username = t_str_new(256);
+	table = auth_request_get_var_expand_table(ctx->request,
auth_request_str_escape);
+	var_expand(username, passdb->username_format, table);
+
 	*resp_r = NULL;

 	resp = calloc(num_msg, sizeof(struct pam_response));
@@ -82,7 +90,7 @@
 		case PAM_PROMPT_ECHO_ON:
 			/* Assume we're asking for user. We might not ever
 			   get here because PAM already knows the user. */
-			string = strdup(ctx->request->user);
+			string = strdup(str_c(username));
 			if (string == NULL)
 				i_fatal_status(FATAL_OUTOFMEM, "Out of memory");
 			break;
@@ -108,12 +116,14 @@
 			}

 			free(resp);
+			str_free(&username);
 			return PAM_CONV_ERR;
 		}

 		resp[i].resp_retcode = PAM_SUCCESS;
 		resp[i].resp = string;
 	}
+	str_free(&username);

 	*resp_r = resp;
 	return PAM_SUCCESS;
@@ -231,7 +241,10 @@
 				       pam_strerror(pamh, status));
 		return status;
 	}
-	auth_request_set_field(request, "user", item, NULL);
+	if (module->pam_allow_transform)
+	{
+		auth_request_set_field(request, "user", item, NULL);
+	}
 	return PAM_SUCCESS;
 }

@@ -257,6 +270,11 @@
 	struct pam_conv conv;
 	enum passdb_result result;
 	int status, status2;
+	const struct var_expand_table *table;
+	string_t *username;
+
+        struct passdb_module *_module = request->passdb->passdb;
+        struct pam_passdb_module *module = (struct pam_passdb_module *)_module;

 	conv.conv = pam_userpass_conv;
 	conv.appdata_ptr = &ctx;
@@ -265,10 +283,15 @@
 	ctx.request = request;
 	ctx.pass = password;

-	status = pam_start(service, request->user, &conv, &pamh);
+	username = t_str_new(256);
+	table = auth_request_get_var_expand_table(request, auth_request_str_escape);
+	var_expand(username, module->username_format, table);
+
+	status = pam_start(service, str_c(username), &conv, &pamh);
 	if (status != PAM_SUCCESS) {
 		auth_request_log_error(request, "pam", "pam_start() failed:
%s",
 				       pam_strerror(pamh, status));
+		str_free(&username);
 		return PASSDB_RESULT_INTERNAL_FAILURE;
 	}

@@ -277,6 +300,7 @@
 	if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) {
 		auth_request_log_error(request, "pam", "pam_end() failed:
%s",
 				       pam_strerror(pamh, status2));
+		str_free(&username);
 		return PASSDB_RESULT_INTERNAL_FAILURE;
 	}

@@ -300,6 +324,7 @@
 		auth_request_set_field(request, "reason",
 				       ctx.failure_msg, NULL);
 	}
+	str_free(&username);
 	return result;
 }

@@ -319,6 +344,7 @@
 	}

 	expanded_service = t_str_new(64);
+
 	var_expand(expanded_service, module->service_name,
 		   auth_request_get_var_expand_table(request, NULL));
 	service = str_c(expanded_service);
@@ -338,6 +364,8 @@

 	module = p_new(pool, struct pam_passdb_module, 1);
 	module->service_name = "dovecot";
+	module->username_format = "%u";
+	module->pam_allow_transform = 1;
 	/* we're caching the password by using directly the plaintext password
 	   given by the auth mechanism */
 	module->module.default_pass_scheme = "PLAIN";
@@ -370,6 +398,12 @@
 			}
 		} else if (t_args[i+1] == NULL) {
 			module->service_name = p_strdup(pool, t_args[i]);
+		} else if (strncmp(t_args[i], "username_format=", 16) == 0) {
+			module->username_format = t_args[i] + 16;
+		} else if (strcmp(t_args[i], "allow_pam_transform=no") == 0 ||
+			strcmp(t_args[i], "-allow_pam_transform") == 0)
+		{
+			module->pam_allow_transform = 0;
 		} else {
 			i_fatal("pam: Unknown setting: %s", t_args[i]);
 		}