This documentation is for Dovecot v2.x, see wiki1 for v1.x documentation.

Dictionary quota

The dictionary quota backend supports both storage and messages quota limits. The current quota is kept in the specified dictionary. The available dictionaries include:

See Dictionary 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:

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: 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.

Examples

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/
  # file backend:
  quota = dict:User quota::file:%h/Maildir/dovecot-quota

  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 for more information, especially about permission issues.

Quota/Dict (last edited 2014-07-25 11:37:22 by TimoSirainen)