I am running Dovecot with system users (userdb passwd), but some of
those users don't have shell accounts on the IMAP server so their shell
on that machine is set to /usr/sbin/nologin. Currently I am using
maildirs and this is not a problem, but I am in the process of switching
to dbox which means I will need a cronjob running 'doveadm purge -A'.
During testing I found that those users with a 'nologin' shell are not
included in the list returned by the userdb iterator, and that the
iterator doesn't honour the first/last_valid_uid settings. This
inconsistency seems undesirable, so the attached patch
- makes lookup perform the same checks as iteration,
- makes the 'nologin' check configurable,
- adds a new optional check that the user owns their home directory.
The last check was the one performed by qmail, and seems to me to be a
more reliable 'is this a real user' check than a nologin shell.
If this patch is applied, the release notes for the next release should
probably mention that system users with a 'nologin' shell will no longer
be allowed to log in to IMAP until the 'auth_check_nologin' setting is
changed from true to false.
Also, there seem to be two first/last_valid_uid settings:
first_valid_uid itself, which is honoured by the storage subsystem, and
auth_first_valid_uid, which is honoured by the 'passwd' userdb. Is this
intentional?
Ben
diff -r bf80034a547d src/auth/auth-settings.c
--- a/src/auth/auth-settings.c Thu Jan 31 18:27:22 2013 +0200
+++ b/src/auth/auth-settings.c Thu Jan 31 22:11:31 2013 +0000
@@ -202,6 +202,8 @@
DEF(SET_TIME, failure_delay),
DEF(SET_UINT, first_valid_uid),
DEF(SET_UINT, last_valid_uid),
+ DEF(SET_BOOL, check_nologin),
+ DEF(SET_BOOL, check_homedir),
DEF(SET_BOOL, verbose),
DEF(SET_BOOL, debug),
@@ -241,6 +243,8 @@
.failure_delay = 2,
.first_valid_uid = 500,
.last_valid_uid = 0,
+ .check_nologin = TRUE,
+ .check_homedir = FALSE,
.verbose = FALSE,
.debug = FALSE,
diff -r bf80034a547d src/auth/auth-settings.h
--- a/src/auth/auth-settings.h Thu Jan 31 18:27:22 2013 +0200
+++ b/src/auth/auth-settings.h Thu Jan 31 22:11:31 2013 +0000
@@ -40,6 +40,8 @@
unsigned int failure_delay;
unsigned int first_valid_uid;
unsigned int last_valid_uid;
+ bool check_nologin;
+ bool check_homedir;
bool verbose, debug, debug_passwords;
const char *verbose_passwords;
diff -r bf80034a547d src/auth/userdb-passwd.c
--- a/src/auth/userdb-passwd.c Thu Jan 31 18:27:22 2013 +0200
+++ b/src/auth/userdb-passwd.c Thu Jan 31 22:11:31 2013 +0000
@@ -10,6 +10,8 @@
#include "time-util.h"
#include "userdb-template.h"
+#include <sys/stat.h>
+
#define USER_CACHE_KEY "%u"
#define PASSWD_SLOW_WARN_MSECS (10*1000)
#define PASSWD_SLOW_MASTER_WARN_MSECS 50
@@ -76,6 +78,41 @@
}
}
+static bool
+passwd_want_pw(struct passwd *pw, const struct auth_settings *set)
+{
+ /* skip entries not in valid UID range.
+ they're users for daemons and such. */
+ if (pw->pw_uid < (uid_t)set->first_valid_uid)
+ return FALSE;
+ if (pw->pw_uid > (uid_t)set->last_valid_uid && set->last_valid_uid != 0)
+ return FALSE;
+
+ if (set->check_nologin) {
+ /* skip entries that don't have a valid shell.
+ they're again probably not real users. */
+ if (strcmp(pw->pw_shell, "/bin/false") == 0 ||
+ strcmp(pw->pw_shell, "/sbin/nologin") == 0 ||
+ strcmp(pw->pw_shell, "/usr/sbin/nologin") == 0)
+ return FALSE;
+ }
+
+ if (set->check_homedir) {
+ int err = errno;
+ struct stat st;
+ int ok;
+
+ /* skip users who don't own their homedirs */
+ ok = (stat(pw->pw_dir, &st) >= 0 &&
+ S_ISDIR(st.st_mode) &&
+ st.st_uid == pw->pw_uid);
+ errno = err;
+ if (!ok) return FALSE;
+ }
+
+ return TRUE;
+}
+
static void passwd_lookup(struct auth_request *auth_request,
userdb_callback_t *callback)
{
@@ -106,6 +143,13 @@
return;
}
+ if (!passwd_want_pw(&pw, auth_request->set)) {
+ auth_request_log_info(auth_request, "passwd",
+ "user has bad uid or homedir");
+ callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
+ return;
+ }
+
auth_request_set_field(auth_request, "user", pw.pw_name, NULL);
auth_request_init_userdb_reply(auth_request);
@@ -137,25 +181,6 @@
return &ctx->ctx;
}
-static bool
-passwd_iterate_want_pw(struct passwd *pw, const struct auth_settings *set)
-{
- /* skip entries not in valid UID range.
- they're users for daemons and such. */
- if (pw->pw_uid < (uid_t)set->first_valid_uid)
- return FALSE;
- if (pw->pw_uid > (uid_t)set->last_valid_uid && set->last_valid_uid != 0)
- return FALSE;
-
- /* skip entries that don't have a valid shell.
- they're again probably not real users. */
- if (strcmp(pw->pw_shell, "/bin/false") == 0 ||
- strcmp(pw->pw_shell, "/sbin/nologin") == 0 ||
- strcmp(pw->pw_shell, "/usr/sbin/nologin") == 0)
- return FALSE;
- return TRUE;
-}
-
static void passwd_iterate_next(struct userdb_iterate_context *_ctx)
{
struct passwd_userdb_iterate_context *ctx =
@@ -173,7 +198,7 @@
errno = 0;
while ((pw = getpwent()) != NULL) {
- if (passwd_iterate_want_pw(pw, set)) {
+ if (passwd_want_pw(pw, set)) {
_ctx->callback(pw->pw_name, _ctx->context);
return;
}