diff options
Diffstat (limited to 'doc/wiki/Quota.Dict.txt')
-rw-r--r-- | doc/wiki/Quota.Dict.txt | 201 |
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) |