userdb-passwd.c revision a10ed8c47534b4c6b6bf2711ccfe577e720a47b4
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* Copyright (c) 2002-2012 Dovecot authors, see the included COPYING file */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "auth-common.h"
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "userdb.h"
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen#ifdef USERDB_PASSWD
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "ioloop.h"
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen#include "ipwd.h"
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen#include "time-util.h"
bb10ebcf076c959c752f583746d83805d7686df8Timo Sirainen#include "userdb-template.h"
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen#define USER_CACHE_KEY "%u"
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen#define PASSWD_SLOW_WARN_MSECS (10*1000)
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen#define PASSWD_SLOW_MASTER_WARN_MSECS 50
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen#define PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL 100
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen#define PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE 5
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainenstruct passwd_userdb_module {
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen struct userdb_module module;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen struct userdb_template *tmpl;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen unsigned int fast_count, slow_count;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen unsigned int slow_warned:1;
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen};
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainenstruct passwd_userdb_iterate_context {
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen struct userdb_iterate_context ctx;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct passwd_userdb_iterate_context *next_waiting;
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen};
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainenstatic struct passwd_userdb_iterate_context *cur_userdb_iter = NULL;
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainenstatic struct timeout *cur_userdb_iter_to = NULL;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainenstatic void
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainenpasswd_check_warnings(struct auth_request *auth_request,
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen struct passwd_userdb_module *module,
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainen const struct timeval *start_tv)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen struct timeval end_tv;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen unsigned int msecs, percentage;
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen if (gettimeofday(&end_tv, NULL) < 0)
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen return;
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen msecs = timeval_diff_msecs(&end_tv, start_tv);
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen if (msecs >= PASSWD_SLOW_WARN_MSECS) {
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen i_warning("passwd: Lookup for %s took %u secs",
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen auth_request->user, msecs/1000);
beb6125ee872e7fed57745ab33e6de99639180f3Timo Sirainen return;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen }
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen if (worker || module->slow_warned)
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen return;
893e5bbd5184ec5c21f47c67c8ea6efbea41f7d0Timo Sirainen
faed8babca9914257f34fb2e603d74016d563b2dTimo Sirainen if (msecs < PASSWD_SLOW_MASTER_WARN_MSECS) {
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen module->fast_count++;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen return;
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen }
1175f27441385a7011629f295f42708f9a3a4ffcTimo Sirainen module->slow_count++;
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen if (module->fast_count + module->slow_count <
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL)
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen return;
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen
46c31f64b9f0949f00b7819f45b22f2d64b2ea27Timo Sirainen percentage = module->slow_count * 100 /
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen (module->slow_count + module->fast_count);
687bb904e1bb76c21a6e392f60c990486b298ea4Timo Sirainen if (percentage < PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE) {
687bb904e1bb76c21a6e392f60c990486b298ea4Timo Sirainen /* start from beginning */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen module->slow_count = module->fast_count = 0;
1b3bb8d39686ed24730cbc31cc9a33dc62c8c6c3Timo Sirainen } else {
7c95b03620a03a43dd72d39608cea5fc77393ad6Timo Sirainen i_warning("passwd: %u%% of last %u lookups took over "
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen "%u milliseconds, "
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen "you may want to set blocking=yes for userdb",
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen percentage, PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL,
aa38d1a0945f0bc13a225d043f53fad2eec666b1Timo Sirainen PASSWD_SLOW_MASTER_WARN_MSECS);
ae8817f05005f57bba32479a610b52d083e2b6ebTimo Sirainen module->slow_warned = TRUE;
a2cbf1d392ee983520451bc9b849a490f28ac298Timo Sirainen }
73e7998716853b5b7621c06aea0022dccda70ad1Timo Sirainen}
a2cbf1d392ee983520451bc9b849a490f28ac298Timo Sirainen
a2cbf1d392ee983520451bc9b849a490f28ac298Timo Sirainenstatic void passwd_lookup(struct auth_request *auth_request,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen userdb_callback_t *callback)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen struct userdb_module *_module = auth_request->userdb->userdb;
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen struct passwd_userdb_module *module =
d6badc27cd6e8d3398877b6766cb0aaeef3a7800Timo Sirainen (struct passwd_userdb_module *)_module;
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen struct passwd pw;
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen struct timeval start_tv;
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen int ret;
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen auth_request_log_debug(auth_request, "passwd", "lookup");
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen
985fa802913c96ce6f2e25bbc788ee39c416a7e0Timo Sirainen if (gettimeofday(&start_tv, NULL) < 0)
1d3f7c1278168d5b1cbfa9a2cc9929a0909056b4Timo Sirainen start_tv.tv_sec = 0;
985fa802913c96ce6f2e25bbc788ee39c416a7e0Timo Sirainen ret = i_getpwnam(auth_request->user, &pw);
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen if (start_tv.tv_sec != 0)
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen passwd_check_warnings(auth_request, module, &start_tv);
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen switch (ret) {
case -1:
auth_request_log_error(auth_request, "passwd",
"getpwnam() failed: %m");
callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
return;
case 0:
auth_request_log_info(auth_request, "passwd", "unknown user");
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);
auth_request_set_userdb_field(auth_request, "system_groups_user",
pw.pw_name);
auth_request_set_userdb_field(auth_request, "uid", dec2str(pw.pw_uid));
auth_request_set_userdb_field(auth_request, "gid", dec2str(pw.pw_gid));
auth_request_set_userdb_field(auth_request, "home", pw.pw_dir);
userdb_template_export(module->tmpl, auth_request);
callback(USERDB_RESULT_OK, auth_request);
}
static struct userdb_iterate_context *
passwd_iterate_init(struct auth_request *auth_request,
userdb_iter_callback_t *callback, void *context)
{
struct passwd_userdb_iterate_context *ctx;
ctx = i_new(struct passwd_userdb_iterate_context, 1);
ctx->ctx.auth_request = auth_request;
ctx->ctx.callback = callback;
ctx->ctx.context = context;
setpwent();
if (cur_userdb_iter == NULL)
cur_userdb_iter = ctx;
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 =
(struct passwd_userdb_iterate_context *)_ctx;
const struct auth_settings *set = _ctx->auth_request->set;
struct passwd *pw;
if (cur_userdb_iter != NULL && cur_userdb_iter != ctx) {
/* we can't support concurrent userdb iteration.
wait until the previous one is done */
ctx->next_waiting = cur_userdb_iter->next_waiting;
cur_userdb_iter->next_waiting = ctx;
return;
}
errno = 0;
while ((pw = getpwent()) != NULL) {
if (passwd_iterate_want_pw(pw, set)) {
_ctx->callback(pw->pw_name, _ctx->context);
return;
}
}
if (errno != 0) {
i_error("getpwent() failed: %m");
_ctx->failed = TRUE;
}
_ctx->callback(NULL, _ctx->context);
}
static void ATTR_NULL(1)
passwd_iterate_next_timeout(void *context ATTR_UNUSED)
{
timeout_remove(&cur_userdb_iter_to);
passwd_iterate_next(&cur_userdb_iter->ctx);
}
static int passwd_iterate_deinit(struct userdb_iterate_context *_ctx)
{
struct passwd_userdb_iterate_context *ctx =
(struct passwd_userdb_iterate_context *)_ctx;
int ret = _ctx->failed ? -1 : 0;
cur_userdb_iter = ctx->next_waiting;
i_free(ctx);
if (cur_userdb_iter != NULL) {
cur_userdb_iter_to =
timeout_add(0, passwd_iterate_next_timeout, NULL);
}
return ret;
}
static struct userdb_module *
passwd_passwd_preinit(pool_t pool, const char *args)
{
struct passwd_userdb_module *module;
const char *value;
module = p_new(pool, struct passwd_userdb_module, 1);
module->module.cache_key = USER_CACHE_KEY;
module->tmpl = userdb_template_build(pool, "passwd", args);
if (userdb_template_remove(module->tmpl, "blocking", &value)) {
module->module.blocking = value == NULL ||
strcasecmp(value, "yes") == 0;
}
/* FIXME: backwards compatibility */
if (!userdb_template_is_empty(module->tmpl))
i_warning("userdb passwd: Move templates args to override_fields setting");
return &module->module;
}
struct userdb_module_interface userdb_passwd = {
"passwd",
passwd_passwd_preinit,
NULL,
NULL,
passwd_lookup,
passwd_iterate_init,
passwd_iterate_next,
passwd_iterate_deinit
};
#else
struct userdb_module_interface userdb_passwd = {
.name = "passwd"
};
#endif