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.
--- 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]);
                }

Reply via email to