This documentation is for Dovecot v2.x, see wiki1 for v1.x documentation.
Differences between revisions 14 and 15
Revision 14 as of 2008-10-18 23:17:27
Size: 3007
Editor: TimoSirainen
Comment: Changed default quota root name to "user"
Revision 15 as of 2008-10-18 23:44:02
Size: 4254
Editor: TimoSirainen
Comment:
Deletions are marked like this. Additions are marked like this.
Line 10: Line 10:
# v1.1: # v1.1+:
Line 16: Line 16:
For example: == v1.0 & v1.1 ==

Example:
Line 19: Line 21:
dict {
  quotadict = mysql:/etc/dovecot-dict-quota.conf
}
Line 21: Line 27:
  quota = dict:storage=10240:messages=1000 mysql:/etc/dovecot-dict-quota.conf
  # v1.1:
  quota = dict:user::mysql:/etc/dovecot-dict-quota.conf
  quota = dict:storage=10240:messages=1000 proxy::quotadict

  # v1.1 + MySQL:
  quota = dict:user::proxy::quotadict
Line 25: Line 32:
} }}} }
}}}
Line 27: Line 35:
However, '''the above example won't really work'''. This is because it would require linking all the binaries with MySQL library, which I didn't really want to do. Currently you'll have to do this via the dictionary proxy (see below). Actually the performance is better that way anyway, and I don't really see a reason not to use it. The above example uses dictionary proxy process (see below), because SQL libraries aren't linked to all Dovecot binaries.
Line 32: Line 40:
# v1.0 and v1.1 only - v1.2 has different configuration
Line 48: Line 57:
== v1.2+ ==

{{{
dict {
  quotadict = mysql:/etc/dovecot-dict-sql.conf
}

plugin {
  # v1.2 + MySQL:
  quota = dict:user::proxy::quotadict
  # v1.2 + file:
  quota = dict:user::file:%h/Maildir/dovecot-quota

  quota_rule = *:storage=10M:messages=1000
}
}}}

The above SQL example uses dictionary proxy process (see below), because SQL libraries aren't linked to all Dovecot binaries. The file example accesses the file directly.

Example {{{dovecot-dict-sql.conf}}}:

{{{
# v1.2+ only:
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)
);
}}}

If you're using PostgreSQL, you'll need a trigger:
{{{
CREATE OR REPLACE FUNCTION merge_quota() RETURNS TRIGGER AS $$
BEGIN
  IF exists(SELECT 1 FROM quota WHERE username = NEW.username) THEN
    UPDATE quota SET bytes = bytes + NEW.bytes,
      messages = messages + NEW.messages
      WHERE username = NEW.username;
    RETURN NULL;
  ELSE
    RETURN NEW;
  END IF;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER mergequota BEFORE INSERT ON quota
   FOR EACH ROW EXECUTE PROCEDURE merge_quota();
}}}
Line 50: Line 126:
With Dovecot v1.1 quota is tracked accurately. With v1.0 you may have a problem: With Dovecot v1.1+ quota is tracked accurately. With v1.0 you may have a problem:
Line 60: Line 136:
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: 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:
Line 64: Line 140:
  quotadict = mysql:/etc/dovecot-dict-quota.conf
} }}}

Example quota plugin configuration for this:

{{{
plugin {
  # v1.0:
  quota = dict:storage=10240:messages=1000 proxy::quotadict
  # v1.1:
  quota = dict:user::proxy::quotadict
  quota_rule = *:storage=10M:messages=1000
} }}}
  quota = mysql:/etc/dovecot-dict-quota.conf
  expire = mysql:/etc/dovecot-dict-expire.conf
}
}}}

Dictionary quota

The dictionary quota backend supports both storage and messages quota limits. The current quota is kept in a dictionary. Currently the only supported dictionary backend is MySQL.

The plugin parameter format is:

# v1.0:
quota = dict:<quota limits> <dictionary URI>
# v1.1+:
quota = dict:<quota root name>:<user name>:<dictionary URI>

If user name is left empty, the logged in username is used (this is probably what you want).

v1.0 & v1.1

Example:

dict {
  quotadict = mysql:/etc/dovecot-dict-quota.conf
}

plugin {
  # v1.0: 10MB and 1000 messages quota limit
  quota = dict:storage=10240:messages=1000 proxy::quotadict

  # v1.1 + MySQL:
  quota = dict:user::proxy::quotadict
  quota_rule = *:storage=10M:messages=1000
}

The above example uses dictionary proxy process (see below), because SQL libraries aren't linked to all Dovecot binaries.

Example dovecot-dict-quota.conf:

# v1.0 and v1.1 only - v1.2 has different configuration
connect = host=localhost dbname=mails user=sqluser password=sqlpass
table = quota
select_field = current
where_field = path
username_field = username 

Create the table like this:

create table quota (
  username varchar(255) not null,
  path varchar(100) not null,
  current integer,
  primary key (username, path)
); 

v1.2+

dict {
  quotadict = mysql:/etc/dovecot-dict-sql.conf
}

plugin {
  # v1.2 + MySQL:
  quota = dict:user::proxy::quotadict
  # v1.2 + file:
  quota = dict:user::file:%h/Maildir/dovecot-quota

  quota_rule = *:storage=10M:messages=1000
}

The above SQL example uses dictionary proxy process (see below), because SQL libraries aren't linked to all Dovecot binaries. The file example accesses the file directly.

Example dovecot-dict-sql.conf:

# v1.2+ only:
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)
);

If you're using PostgreSQL, you'll need a trigger:

CREATE OR REPLACE FUNCTION merge_quota() RETURNS TRIGGER AS $$
BEGIN
  IF exists(SELECT 1 FROM quota WHERE username = NEW.username) THEN
    UPDATE quota SET bytes = bytes + NEW.bytes,
      messages = messages + NEW.messages
      WHERE username = NEW.username;
    RETURN NULL;
  ELSE
    RETURN NEW;
  END IF;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER mergequota BEFORE INSERT ON quota
   FOR EACH ROW EXECUTE PROCEDURE merge_quota();

v1.0 Inaccuracy problems

With Dovecot v1.1+ quota is tracked accurately. With v1.0 you may have a problem:

If two IMAP clients do an expunge at the same time, the quota is reduced twice as much. Maildir++ backend also has the same problem, but it's not that big of a problem with it because it recalculates the quota once in a while anyway. Dict quota is recalculated only if the quota goes below zero (v1.0.rc30+ only).

So either you'll have to trust your users not to abuse this problem, or you could create a nightly cronjob to delete all rows from the SQL quota table to force a daily recalculation. The recalculation will of course slow down the server.

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-dict-quota.conf
  expire = mysql:/etc/dovecot-dict-expire.conf
}

None: Quota/Dict (last edited 2017-04-12 09:00:54 by TimoSirainen)