summaryrefslogtreecommitdiffstats
path: root/src/auth/userdb-passwd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/userdb-passwd.c')
-rw-r--r--src/auth/userdb-passwd.c253
1 files changed, 253 insertions, 0 deletions
diff --git a/src/auth/userdb-passwd.c b/src/auth/userdb-passwd.c
new file mode 100644
index 0000000..3c99102
--- /dev/null
+++ b/src/auth/userdb-passwd.c
@@ -0,0 +1,253 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "auth-common.h"
+#include "userdb.h"
+
+#ifdef USERDB_PASSWD
+
+#include "ioloop.h"
+#include "ipwd.h"
+#include "time-util.h"
+#include "userdb-template.h"
+
+#define USER_CACHE_KEY "%u"
+#define PASSWD_SLOW_WARN_MSECS (10*1000)
+#define PASSWD_SLOW_MASTER_WARN_MSECS 50
+#define PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL 100
+#define PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE 5
+
+struct passwd_userdb_module {
+ struct userdb_module module;
+ struct userdb_template *tmpl;
+
+ unsigned int fast_count, slow_count;
+ bool slow_warned:1;
+};
+
+struct passwd_userdb_iterate_context {
+ struct userdb_iterate_context ctx;
+ struct passwd_userdb_iterate_context *next_waiting;
+};
+
+static struct passwd_userdb_iterate_context *cur_userdb_iter = NULL;
+static struct timeout *cur_userdb_iter_to = NULL;
+
+static void
+passwd_check_warnings(struct auth_request *auth_request,
+ struct passwd_userdb_module *module,
+ const struct timeval *start_tv)
+{
+ struct timeval end_tv;
+ unsigned int msecs, percentage;
+
+ i_gettimeofday(&end_tv);
+
+ msecs = timeval_diff_msecs(&end_tv, start_tv);
+ if (msecs >= PASSWD_SLOW_WARN_MSECS) {
+ e_warning(authdb_event(auth_request), "Lookup for %s took %u secs",
+ auth_request->fields.user, msecs/1000);
+ return;
+ }
+ if (worker || module->slow_warned)
+ return;
+
+ if (msecs < PASSWD_SLOW_MASTER_WARN_MSECS) {
+ module->fast_count++;
+ return;
+ }
+ module->slow_count++;
+ if (module->fast_count + module->slow_count <
+ PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL)
+ return;
+
+ percentage = module->slow_count * 100 /
+ (module->slow_count + module->fast_count);
+ if (percentage < PASSDB_SLOW_MASTER_WARN_MIN_PERCENTAGE) {
+ /* start from beginning */
+ module->slow_count = module->fast_count = 0;
+ } else {
+ e_warning(authdb_event(auth_request),
+ "%u%% of last %u lookups took over "
+ "%u milliseconds, "
+ "you may want to set blocking=yes for userdb",
+ percentage, PASSDB_SLOW_MASTER_WARN_COUNT_INTERVAL,
+ PASSWD_SLOW_MASTER_WARN_MSECS);
+ module->slow_warned = TRUE;
+ }
+}
+
+static void passwd_lookup(struct auth_request *auth_request,
+ userdb_callback_t *callback)
+{
+ struct userdb_module *_module = auth_request->userdb->userdb;
+ struct passwd_userdb_module *module =
+ (struct passwd_userdb_module *)_module;
+ struct passwd pw;
+ struct timeval start_tv;
+ const char *error;
+ int ret;
+
+ e_debug(authdb_event(auth_request), "lookup");
+
+ i_gettimeofday(&start_tv);
+ ret = i_getpwnam(auth_request->fields.user, &pw);
+ if (start_tv.tv_sec != 0)
+ passwd_check_warnings(auth_request, module, &start_tv);
+
+ switch (ret) {
+ case -1:
+ e_error(authdb_event(auth_request),
+ "getpwnam() failed: %m");
+ callback(USERDB_RESULT_INTERNAL_FAILURE, auth_request);
+ return;
+ case 0:
+ auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB);
+ callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
+ return;
+ }
+
+ auth_request_set_field(auth_request, "user", pw.pw_name, NULL);
+
+ 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);
+
+ if (userdb_template_export(module->tmpl, auth_request, &error) < 0) {
+ e_error(authdb_event(auth_request),
+ "Failed to expand template: %s", error);
+ callback(USERDB_RESULT_INTERNAL_FAILURE, 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;
+ if (pw->pw_gid < (gid_t)set->first_valid_gid)
+ return FALSE;
+ if (pw->pw_gid > (gid_t)set->last_valid_gid && set->last_valid_gid != 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;
+ }
+
+ /* reset errno since it might have been set when we got here */
+ errno = 0;
+ while ((pw = getpwent()) != NULL) {
+ if (passwd_iterate_want_pw(pw, set)) {
+ _ctx->callback(pw->pw_name, _ctx->context);
+ return;
+ }
+ /* getpwent might set errno to something even if it
+ returns non-NULL. */
+ errno = 0;
+ }
+ if (errno != 0) {
+ e_error(authdb_event(_ctx->auth_request),
+ "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);
+ }
+ endpwent();
+ 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.default_cache_key = USER_CACHE_KEY;
+ module->tmpl = userdb_template_build(pool, "passwd", args);
+ module->module.blocking = TRUE;
+
+ if (userdb_template_remove(module->tmpl, "blocking", &value))
+ module->module.blocking = 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