diff --git a/core/admin/mailu/internal/views/dovecot.py b/core/admin/mailu/internal/views/dovecot.py index 0d56950b..10f3d5fb 100644 --- a/core/admin/mailu/internal/views/dovecot.py +++ b/core/admin/mailu/internal/views/dovecot.py @@ -19,6 +19,11 @@ def dovecot_passdb_dict(user_email): "allow_nets": ",".join(allow_nets) }) +@internal.route("/dovecot/userdb/") +def dovecot_userdb_dict_list(): + return flask.jsonify([ + user[0] for user in models.User.query.filter(models.User.enabled.is_(True)).with_entities(models.User.email).all() + ]) @internal.route("/dovecot/userdb/") def dovecot_userdb_dict(user_email): diff --git a/core/base/libs/podop/podop/dovecot.py b/core/base/libs/podop/podop/dovecot.py index 40b48eef..18956dd4 100644 --- a/core/base/libs/podop/podop/dovecot.py +++ b/core/base/libs/podop/podop/dovecot.py @@ -77,7 +77,7 @@ class DictProtocol(asyncio.Protocol): logging.debug("Client {}.{} type {}, user {}, dict {}".format( self.major, self.minor, self.value_type, self.user, dict_name)) - async def process_lookup(self, key, user=None): + async def process_lookup(self, key, user=None, is_iter=False): """ Process a dict lookup message """ logging.debug("Looking up {} for {}".format(key, user)) @@ -93,10 +93,33 @@ class DictProtocol(asyncio.Protocol): response = result else: response = json.dumps(result).encode("ascii") - return self.reply(b"O", response) + logging.debug("Replying {}".format(key)) + return self.reply(b"O", (key_type+'/'+key).encode("utf8"), response, end=True) if is_iter else self.reply(b"O", response) except KeyError: return self.reply(b"N") + async def process_iterate(self, flags, max_rows, path, user=None): + """ Process an iterate command + """ + logging.debug("Iterate flags {} max_rows {} on {} for {}".format(flags, max_rows, path, user)) + # Priv and shared keys are handled slighlty differently + key_type, key = path.decode("utf8").split("/", 1) + max_rows = int(max_rows.decode("utf-8")) + flags = int(flags.decode("utf-8")) + if flags != 0: # not implemented + return self.reply(b"F") + try: + result = await self.dict.iter(key) + logging.debug("Found {} entries: {}".format(len(result), result)) + returned_results = 0 + for k in result: + if max_rows == 0 or returned_results < max_rows: + await self.process_lookup((path.decode("utf8")+k).encode("utf8"), user, is_iter=True) + returned_results += 1 + return self.reply(b"\n") # ITER_FINISHED + except KeyError: + return self.reply(b"F") + def process_begin(self, transaction_id, user=None): """ Process a dict begin message """ @@ -126,11 +149,12 @@ class DictProtocol(asyncio.Protocol): del self.transactions_user[transaction_id] return self.reply(b"O", transaction_id) - def reply(self, command, *args): + def reply(self, command, *args, end=True): logging.debug("Replying {} with {}".format(command, args)) self.transport.write(command) self.transport.write(b"\t".join(map(tabescape, args))) - self.transport.write(b"\n") + if end: + self.transport.write(b"\n") @classmethod def factory(cls, table_map): @@ -141,6 +165,7 @@ class DictProtocol(asyncio.Protocol): COMMANDS = { ord("H"): process_hello, ord("L"): process_lookup, + ord("I"): process_iterate, ord("B"): process_begin, ord("C"): process_commit, ord("S"): process_set diff --git a/core/dovecot/conf/auth.conf b/core/dovecot/conf/auth.conf index 44a874ba..1f122416 100644 --- a/core/dovecot/conf/auth.conf +++ b/core/dovecot/conf/auth.conf @@ -1,5 +1,6 @@ uri = proxy:/tmp/podop.socket:auth iterate_disable = yes +iterate_prefix = 'userdb/' default_pass_scheme = plain password_key = passdb/%u user_key = userdb/%u diff --git a/towncrier/newsfragments/2498.feature b/towncrier/newsfragments/2498.feature new file mode 100644 index 00000000..961b6a84 --- /dev/null +++ b/towncrier/newsfragments/2498.feature @@ -0,0 +1 @@ +Implement the required glue to make "doveadm -A" work