6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Attribute Handling in Dovecot
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * =============================
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * What IMAP & doveadm users see gets translated into one of several things
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * depending on if we're operating on a mailbox or on server metadata (""
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * mailbox in IMAP parlance). Consider these examples:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Here "foo" can be any RFC defined attribute name, or a vendor-prefixed
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * non-standard name. (Our vendor prefix is "vendor/vendor.dovecot".)
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * In all cases, the "/private" and "/shared" user visible prefixes get
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * replaced by priv/<GUID> and shared/<GUID>, respectively. (Here, <GUID>
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * is the GUID of the mailbox with which the attribute is associated.) This
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * way, attributes for all mailboxes can be stored in a single dict. For
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * example, the above examples would map to:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<GUID>/foo
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * shared/<GUID>/foo
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * More concrete examples:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<GUID>/comment
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Server attributes, that is attributes not associated with a mailbox, are
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * stored in the INBOX mailbox with a special prefix -
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * vendor/vendor.dovecot/pvt/server. For example, the server attribute
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * /private/comment gets mapped to:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<INBOX GUID>/vendor/vendor.dovecot/pvt/server/comment
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * This means that if we set a /private/comment server attribute as well as
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * /private/comment INBOX mailbox attribute, we'll see the following paths
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * used in the dict:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<INBOX GUID>/comment <- mailbox attr
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<INBOX GUID>/vendor/vendor.dovecot/pvt/server/comment <- server attr
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * The case of vendor specific server attributes is a bit confusing, but
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * consistent. For example, this server attribute:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * It will get mapped to:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<INBOX GUID>/vendor/vendor.dovecot/pvt/server/vendor/vendor.dovecot/abc
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * \----- server attr prefix -----/ \-- server attr name ---/
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Internal Attributes
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * -------------------
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * The final aspect of attribute handling in Dovecot are the so called
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * "internal attributes".
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * The easiest way to explain internal attributes is to summarize attributes
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * in general. Attributes are just <key,value> pairs that are stored in a
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * dict. The key is mangled according to the above rules before passed to
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * the dict code. That is, the key already encodes whether the attribute is
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * private or shared, the GUID of the mailbox (or of INBOX for server
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * attributes), etc. There is no processing of the value. It is stored and
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * returned to clients verbatim.
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Internal attributes, on the other hand, are special cased attributes.
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * That is, the code contains a list of specific attribute names and how to
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * handle them. Each internal attribute is defined by a struct
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * mailbox_attribute_internal. It contains the pre-parsed name of the
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * attribute (type, key, and flags), and how to handle getting and setting
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * of the attribute (rank, get, and set).
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * The values for these attributes may come from two places - from the
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * attributes dict, or from the get function pointer. Which source to use
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * is identified by the rank (MAIL_ATTRIBUTE_INTERNAL_RANK_*).
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * In general, a user (IMAP or doveadm) can access all attributes for a
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * mailbox. The one exception are attributes under:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Which as you may recall map to:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * shared/<GUID>/vendor/vendor.dovecot/pvt
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * These are deemed internal to Dovecot, and therefore of no concern to the
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Server attributes have a similar restriction. That is, attributes
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * beginning with the following are not accessible:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * However since server attributes are stored under the INBOX mailbox, these
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * paths map to:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<INBOX GUID>/vendor/vendor.dovecot/pvt/server/vendor/vendor.dovecot/pvt
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * shared/<INBOX GUID>/vendor/vendor.dovecot/pvt/server/vendor/vendor.dovecot/pvt
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * As a result, the code performs access checks via the
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * MAILBOX_ATTRIBUTE_KEY_IS_USER_ACCESSIBLE() macro to make sure that the
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * user is allowed access to the attribute.
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Since every path stored in the dict begins with priv/<GUID> or
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * shared/<GUID>, these prefixes are often omitted. This also matches the
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * internal implementation where the priv/ or shared/ prefix is specified
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * using an enum, and only the path after the GUID is handled as a string.
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * For example:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<GUID>/vendor/vendor.dovecot/pvt/server/foo
211c638d81d382517d196ad47565e0d85012c927klemens * would be referred to as:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Since some of the generated paths are very long, developers often use a
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * shorthand to refer to some of these paths. For example,
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * vendor/vendor.dovecot/pvt/server/vendor/vendor.dovecot/pvt
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * Which when fully specified with a type and INBOX's GUID would turn into
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * one of the following:
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * priv/<GUID>/vendor/vendor.dovecot/pvt/server/vendor/vendor.dovecot/pvt
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek * shared/<GUID>/vendor/vendor.dovecot/pvt/server/vendor/vendor.dovecot/pvt
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* RFC 5464 specifies that this is vendor/<vendor-token>/. The registered
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch vendor-tokens always begin with "vendor." so there's some redundancy.. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch#define MAILBOX_ATTRIBUTE_PREFIX_DOVECOT "vendor/vendor.dovecot/"
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Prefix used for attributes reserved for Dovecot's internal use. Normal
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch users cannot access these in any way. */
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen/* Server attributes are currently stored in INBOX under this private prefix.
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen They're under the pvt/ prefix so they won't be listed as regular INBOX
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen attributes, but unlike other pvt/ attributes it's actually possible to
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen access these attributes as regular users.
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen If INBOX is deleted, attributes under this prefix are preserved. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch#define MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER \
69e0311cb068f20b0f285a30263068f87bc78ce3Timo Sirainen/* User can get/set all non-pvt/ attributes and also pvt/server/
69e0311cb068f20b0f285a30263068f87bc78ce3Timo Sirainen (but not pvt/server/pvt/) attributes. */
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen#define MAILBOX_ATTRIBUTE_KEY_IS_USER_ACCESSIBLE(key) \
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen (strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT, \
7f0fad65a36fab61739a50ea110e243b225d5d18Timo Sirainen strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) != 0 || \
69e0311cb068f20b0f285a30263068f87bc78ce3Timo Sirainen (strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER, \
69e0311cb068f20b0f285a30263068f87bc78ce3Timo Sirainen strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER)) == 0 && \
69e0311cb068f20b0f285a30263068f87bc78ce3Timo Sirainen strncmp(key, MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT, \
69e0311cb068f20b0f285a30263068f87bc78ce3Timo Sirainen strlen(MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT)) != 0))
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch /* mailbox_attribute_set() can set either value or value_stream.
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch mailbox_attribute_get() returns only values, but
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch mailbox_attribute_get_stream() may return either value or
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch value_stream. The caller must unreference the returned streams. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch /* Last time the attribute was changed (0 = unknown). This may be
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch returned even for values that don't exist anymore. */
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch * Internal attribute
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch /* The internal attribute serves only as a source for a default value
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch when the normal mailbox attribute storage has no entry for this
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch attribute. Otherwise it is ignored. The `set' function is called
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch only as a notification, not with the intention to store the value.
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch The value is always assigned to the normal mailbox attribute storage.
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch /* The internal attribute serves as the main source of the attribute
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch value. If the `get' function returns 0, the normal mailbox attribute
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch storage is attempted to obtain the value. The `set' function is
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch called only as a notification, not with the intention to store the
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch value. The value is assigned to the normal mailbox attribute storage.
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch /* The value for the internal attribute is never read from the normal
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch mailbox attribute storage. If the `set' function is NULL, the
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch attribute is read-only. If it is not NULL it is used to assign the
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch attribute value; it is not assigned to the normal mailbox attribute
72baffa319d909f6e496f7f475f83e5b37b62debTimo Sirainen /* Apply this attribute to the given key and its children. */
6dd167432ab004909073d05a6e6cbdeb5ff28721Josef 'Jeff' Sipek const char *key; /* relative to the GUID, e.g., "comment" */
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch /* Get the value of this internal attribute */
bd6a8056771b6150685dea319ab5a94e021d17f1Josef 'Jeff' Sipek /* Set the value of this internal attribute */
72baffa319d909f6e496f7f475f83e5b37b62debTimo Sirainen int (*set)(struct mailbox_transaction_context *t, const char *key,
bd6a8056771b6150685dea319ab5a94e021d17f1Josef 'Jeff' Sipek const struct mail_attribute_value *value);
bf79967ee56a3f7c99012997c1834452594fed41Stephan Bosch const struct mailbox_attribute_internal *iattr);
3c7dbe9bd199bb88a9261136876a2d683680d405Timo Sirainen const struct mailbox_attribute_internal *iattrs, unsigned int count);
0206ab42ade102a3170b3a536d1c8e9f2c1340c8Timo Sirainen const struct mailbox_attribute_internal *iattr);
0206ab42ade102a3170b3a536d1c8e9f2c1340c8Timo Sirainen const struct mailbox_attribute_internal *iattrs, unsigned int count);
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch * Attribute API
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Set mailbox attribute key to value. The key should be compatible with
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch IMAP METADATA, so for Dovecot-specific keys use
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch MAILBOX_ATTRIBUTE_PREFIX_DOVECOT. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Boschint mailbox_attribute_set(struct mailbox_transaction_context *t,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch enum mail_attribute_type type, const char *key,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Delete mailbox attribute key. This is just a wrapper to
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch mailbox_attribute_set() with value->value=NULL. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Boschint mailbox_attribute_unset(struct mailbox_transaction_context *t,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch enum mail_attribute_type type, const char *key);
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Returns value for mailbox attribute key. Returns 1 if value was returned,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch 0 if value wasn't found (set to NULL), -1 if error */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch enum mail_attribute_type type, const char *key,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Same as mailbox_attribute_get(), but the returned value may be either an
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch input stream or a string. */
9f37ef2a9192e7d47e3d7ac959080fd01120f2e9Aki Tuomiint mailbox_attribute_get_stream(struct mailbox *box,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch enum mail_attribute_type type, const char *key,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Iterate through mailbox attributes of the given type. The prefix can be used
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch to restrict what attributes are returned. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Boschmailbox_attribute_iter_init(struct mailbox *box, enum mail_attribute_type type,
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch const char *prefix);
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Bosch/* Returns the attribute key or NULL if there are no more attributes. */
146f9076cd456ea1e9b3f8536456d9d3c962fadbStephan Boschconst char *mailbox_attribute_iter_next(struct mailbox_attribute_iter *iter);