summaryrefslogtreecommitdiffstats
path: root/doc/wiki/Quota.Dict.txt
diff options
context:
space:
mode:
Diffstat (limited to 'doc/wiki/Quota.Dict.txt')
-rw-r--r--doc/wiki/Quota.Dict.txt201
1 files changed, 201 insertions, 0 deletions
diff --git a/doc/wiki/Quota.Dict.txt b/doc/wiki/Quota.Dict.txt
new file mode 100644
index 0000000..e346653
--- /dev/null
+++ b/doc/wiki/Quota.Dict.txt
@@ -0,0 +1,201 @@
+Dictionary quota
+================
+
+*NOTE:* Using the <count> [Quota.Count.txt] backend (possibly together with
+<quota_clone> [Plugins.QuotaClone.txt] plugin) is now preferred to using this
+backend. It has less of a chance of quota usage becoming wrong.
+
+The /dictionary/ quota backend supports both *storage* and *messages* quota
+limits. The current quota is kept in the specified <dictionary>
+[Dictionary.txt]. The available dictionaries include:
+
+ * SQL
+ * Redis
+ * Flat files
+
+See <Dictionary.txt> for full description of the available backends.
+
+The quota root format is:
+
+---%<-------------------------------------------------------------------------
+quota = dict:<quota root name>:<username>[:<option>[...]]:<dictionary URI>
+---%<-------------------------------------------------------------------------
+
+If /username/ is left empty, the logged in username is used (this is typically
+what you want). Another useful username is '%d' for supporting domain-wide
+quotas.
+
+The supported options are:
+
+ * noenforcing: Don't enforce quota limits, only track them.
+ * ignoreunlimited: If user has unlimited quota, don't track it.
+ * ns=<prefix>: This quota root is tracked only for the given namespace.
+ * hidden: Hide the quota root from IMAP GETQUOTA* commands.
+ * no-unset: When recalculating quota, don't unset the quota first. This is
+ needed if you wish to store the quota usage among other data in the same SQL
+ row - otherwise the entire row could get deleted. Note that the unset is
+ required with PostgreSQL or the merge_quota() trigger doesn't work
+ correctly. (v2.2.20+)
+
+NOTE: The dictionary stores only the current quota usage. The quota limits are
+still configured in userdb the same way as with other quota backends.
+
+NOTE2: By default the quota dict may delete rows from the database when it
+wants to rebuild the quota. You must use a separate table that contains only
+the quota information, or you'll lose the other data. This can be avoided with
+the "no-unset" parameter.
+
+Examples
+--------
+
+Simple per-user flat file
+-------------------------
+
+This will create one quota-accounting file for each user.
+
+The Dovecot user process (imap, pop3 or lda) needs write access to this file,
+so %h or mail_location are good candidates to store it.
+
+*Warning*: if a user has shell or file access to this location, he can mangle
+his quota file, thus overcoming his quota limit by lying about his used
+capacity.
+
+---%<-------------------------------------------------------------------------
+plugin {
+ quota = dict:User quota::file:%h/mdbox/dovecot-quota
+ quota_rule = *:storage=10M:messages=1000
+}
+---%<-------------------------------------------------------------------------
+
+Server-based dictionaries
+-------------------------
+
+---%<-------------------------------------------------------------------------
+plugin {
+ # SQL backend:
+ quota = dict:User quota::proxy::sqlquota
+ # Redis backend (v2.1.9+):
+ quota = dict:User quota::redis:host=127.0.0.1:prefix=user/
+
+ quota_rule = *:storage=10M:messages=1000
+}
+dict {
+ sqlquota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
+}
+---%<-------------------------------------------------------------------------
+
+The above SQL example uses dictionary proxy process (see below), because SQL
+libraries aren't linked to all Dovecot binaries. The file and Redis examples
+use direct access.
+
+Example 'dovecot-dict-sql.conf.ext':
+
+---%<-------------------------------------------------------------------------
+connect = host=localhost dbname=mails user=sqluser password=sqlpass
+map {
+ pattern = priv/quota/storage
+ table = quota
+ username_field = username
+ value_field = bytes
+}
+map {
+ pattern = priv/quota/messages
+ table = quota
+ username_field = username
+ value_field = messages
+}
+---%<-------------------------------------------------------------------------
+
+Create the table like this:
+
+---%<-------------------------------------------------------------------------
+CREATE TABLE quota (
+ username varchar(100) not null,
+ bytes bigint not null default 0,
+ messages integer not null default 0,
+ primary key (username)
+);
+---%<-------------------------------------------------------------------------
+
+MySQL uses the following queries to update the quota. You need suitable
+privileges.
+
+---%<-------------------------------------------------------------------------
+INSERT INTO table (bytes,username) VALUES ('112497180','foo@example.com') ON
+DUPLICATE KEY UPDATE bytes='112497180';
+INSERT INTO table (messages,username) VALUES ('1743','foo@example.com') ON
+DUPLICATE KEY UPDATE messages='1743';
+UPDATE table SET bytes=bytes-14433,messages=messages-2 WHERE username =
+'foo@example.com';
+DELETE FROM table WHERE username = 'foo@example.com';
+---%<-------------------------------------------------------------------------
+
+If you're using SQLite, then take a look at the trigger in this post:
+http://dovecot.org/pipermail/dovecot/2013-July/091421.html
+
+If you're using PostgreSQL, you'll need a trigger:
+
+---%<-------------------------------------------------------------------------
+CREATE OR REPLACE FUNCTION merge_quota() RETURNS TRIGGER AS $$
+BEGIN
+ IF NEW.messages < 0 OR NEW.messages IS NULL THEN
+ -- ugly kludge: we came here from this function, really do try to insert
+ IF NEW.messages IS NULL THEN
+ NEW.messages = 0;
+ ELSE
+ NEW.messages = -NEW.messages;
+ END IF;
+ return NEW;
+ END IF;
+
+ LOOP
+ UPDATE quota SET bytes = bytes + NEW.bytes,
+ messages = messages + NEW.messages
+ WHERE username = NEW.username;
+ IF found THEN
+ RETURN NULL;
+ END IF;
+
+ BEGIN
+ IF NEW.messages = 0 THEN
+ INSERT INTO quota (bytes, messages, username)
+ VALUES (NEW.bytes, NULL, NEW.username);
+ ELSE
+ INSERT INTO quota (bytes, messages, username)
+ VALUES (NEW.bytes, -NEW.messages, NEW.username);
+ END IF;
+ return NULL;
+ EXCEPTION WHEN unique_violation THEN
+ -- someone just inserted the record, update it
+ END;
+ END LOOP;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mergequota BEFORE INSERT ON quota
+ FOR EACH ROW EXECUTE PROCEDURE merge_quota();
+---%<-------------------------------------------------------------------------
+
+Dictionary proxy server
+-----------------------
+
+To avoid each process making a new SQL connection, you can make all dictionary
+communications go through a dictionary server process which keeps the
+connections permanently open.
+
+The dictionary server is referenced with URI 'proxy:<dictionary server socket
+path>:<dictionary name>'. The socket path may be left empty if you haven't
+changed 'base_dir' setting in 'dovecot.conf'. Otherwise set it to
+'<base_dir>/dict-server'. The dictionary names are configured in
+'dovecot.conf'. For example:
+
+---%<-------------------------------------------------------------------------
+dict {
+ quota = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
+ expire = mysql:/etc/dovecot/dovecot-dict-sql.conf.ext
+}
+---%<-------------------------------------------------------------------------
+
+See <Dict.txt> for more information, especially about permission issues.
+
+(This file was created from the wiki on 2019-06-19 12:42)