Skip to main content

Access control

PluginACL
CapabilitiesACL, RIGHTS=texk, and LIST-MYRIGHTS when LIST-EXTENDED is loaded
RFCRFC 4314, RFC 8440
Server optionaclOwner (default "testuser")
CommandsSETACL, DELETEACL, GETACL, LISTRIGHTS, MYRIGHTS, and the MYRIGHTS return option of LIST

ImapKit has a single mailbox tree that every user shares (see Authentication). With the ACL plugin, the owner has every right on every mailbox, and every other user only gets what the ACL of a mailbox grants them. This is how you test a client against shared mailboxes, read-only folders and permission errors.

Setting up users and ACLs​

The owner is the user named by the aclOwner option, testuser by default. Add other users with the users option, and give them rights with the acl property of a mailbox in the storage:

const server = imapkit({
plugins: ['ACL', 'LIST-EXTENDED'],
users: {
testuser: { password: 'testpass' },
otheruser: { password: 'secret' }
},
storage: {
INBOX: { acl: { otheruser: 'lr', anyone: 'l' }, messages: [/* four messages */] },
'': {
separator: '/',
folders: {
Archive: { acl: { otheruser: 'lrswite' } },
Shared: { acl: { otheruser: 'lrswikte', '-otheruser': 't' } },
Sent: {}
}
}
}
});

A user other than the owner gets the rights granted to their user name and to anyone, minus the negative rights of -username and -anyone (RFC 4314 section 2). An invalid ACL in the storage is not checked when the server is created: the first command that needs the ACL of that mailbox fails with NO [SERVERBUG].

ACLs can also be changed with SETACL and DELETEACL, or from your test with the control API (server.control.getAcl(), setAcl(), deleteAcl(), see Plugin operations).

Rights​

RightMeaning
llookup: the mailbox is visible in LIST and LSUB
rread: SELECT, EXAMINE and STATUS
skeep the \Seen flag
wwrite flags other than \Seen and \Deleted
iinsert: APPEND, COPY and MOVE into the mailbox
ppost (accepted and listed, there is no submission)
kcreate mailboxes below this one
xdelete the mailbox, rename it
tset and clear \Deleted
eexpunge
aadminister: change the ACL

RIGHTS=texk tells clients that t, e, x and k can be granted separately. The obsolete c and d rights are accepted as kx and et, and are added to ACL and MYRIGHTS responses (RFC 4314 section 2.1.1). Unknown rights and uppercase rights are BAD, and so are empty identifiers and identifiers with control characters or invalid UTF-8 (RFC 4314 section 3).

The rights of the owner can not be changed:

C: A3 GETACL INBOX
S: * ACL INBOX testuser lrswipkxteacd otheruser lr anyone l
S: A3 OK Getacl complete
C: A4 SETACL Archive otheruser +a
S: A4 OK Setacl complete
C: A5 LISTRIGHTS INBOX otheruser
S: * LISTRIGHTS INBOX otheruser "" l r s w i p k x t e a c d
S: A5 OK Listrights complete
C: A6 SETACL INBOX otheruser lrX
S: A6 BAD Uppercase rights are not allowed
C: A7 SETACL INBOX testuser lr
S: A7 NO [CANNOT] Rights of the mailbox owner can not be changed

Enforcement​

The owner bypasses every check. For other users, ImapKit enforces the rights as RFC 4314 section 4 describes:

CommandNeeds
LIST, LSUBmailboxes without l are left out
SELECT, EXAMINE, STATUSr
SUBSCRIBEl
SELECT read-writeany of i, e, s, w, t, otherwise the mailbox is opened READ-ONLY. PERMANENTFLAGS only lists the flags the user can change
STOREs for \Seen, t for \Deleted, w for other flags. Only the flags the user has rights for change, and NO [NOPERM] if none could
FETCHwithout s, FETCH does not set \Seen
APPEND, COPYi on the target. Only the flags the user has rights for are kept
MOVEi on the target, t and e on the source (RFC 6851 section 4.2)
REPLACElike MOVE (RFC 8508 section 4.1)
EXPUNGEe. CLOSE without e closes the mailbox without expunging
CREATEk on the nearest existing parent, so other users can not create top level mailboxes
DELETEx
RENAMEx on the mailbox and k on the new parent
GETACL, SETACL, DELETEACL, LISTRIGHTSa
MYRIGHTSany of l, r, i, k, x, a

APPEND and REPLACE are refused before the message literal is sent. The rights on the selected mailbox are taken when it is selected. A new mailbox inherits the ACL of its parent, and DELETE removes the ACL.

Missing rights are answered with NO [NOPERM]. When the user does not have l either, the answer is the same as for a mailbox that does not exist, so the existence of the mailbox is not disclosed (RFC 4314 section 6).

Logged in as otheruser with the storage above:

C: A2 MYRIGHTS INBOX
S: * MYRIGHTS INBOX lr
S: A2 OK Myrights complete
C: A3 LIST "" "*" RETURN (MYRIGHTS)
S: * LIST (\HasNoChildren) "/" "INBOX"
S: * MYRIGHTS INBOX lr
S: * LIST (\HasNoChildren) "/" "Archive"
S: * MYRIGHTS Archive lrswited
S: * LIST (\HasNoChildren) "/" "Shared"
S: * MYRIGHTS Shared lrswikecd
S: A3 OK Completed
C: A4 SELECT INBOX
S: * FLAGS (\Answered \Flagged \Draft \Deleted \Seen)
S: * OK [PERMANENTFLAGS ()] No permanent flags permitted
S: * 4 EXISTS
S: * 0 RECENT
S: * OK [UNSEEN 2] First unseen message
S: * OK [UIDVALIDITY 1] UIDs valid
S: * OK [UIDNEXT 5] Predicted next UID
S: A4 OK [READ-ONLY] Completed
C: A5 STORE 2 +FLAGS (\Flagged)
S: A5 NO [CLIENTBUG] Mailbox is read-only
C: A6 GETACL INBOX
S: A6 NO [NOPERM] Permission denied
C: A7 SELECT Sent
S: A7 NO [NONEXISTENT] Mailbox does not exist
C: A8 CREATE Mine
S: A8 NO [NOPERM] Permission denied

Sent has no ACL, so otheruser does not have l and gets the same answer as for a missing mailbox. INBOX opens read-only, as lr holds none of i, e, s, w and t.

With other plugins​

PluginEffect
LIST-EXTENDEDLIST-MYRIGHTS: the MYRIGHTS return option of LIST (RFC 8440)
LIST-STATUSmailboxes without r get no STATUS response and are listed with \Noselect (RFC 5819 section 2)
METADATAGETMETADATA and SETMETADATA on a mailbox need l and any of r, s, w, i, p (RFC 5464 section 3.3). Unsolicited METADATA responses only go to sessions with these rights
QUOTAGETQUOTAROOT only lists the MAILBOX resource without r on the mailbox. SETQUOTA needs a on every mailbox of the quota root (RFC 9208 section 6)
MULTISEARCHmailboxes without r are skipped, and without l unless named under mailboxes or as a subtree root
NOTIFYonly mailboxes with l and r are reported, granting or revoking l counts as a MailboxName event
CATENATEURLs of mailboxes the user can not read are refused with NO [BADURL]

The ACL plugin wraps the commands of other plugins too (MOVE, UID EXPUNGE, REPLACE ...), in any load order.