Folder Permissions

Exchange Permissions

Every object in the Exchange database has a security descriptor that controls access to it. (This includes both folders and items, but security descriptors for items are virtually always inherited from their parent folders in well-defined ways, so we don't bother thinking about them.)

The Exchange 2000 Web Storage System has a nifty sytem for translating Windows security descriptors into XML and back, which unfortunately we cannot use, because it's buggy. So we have to generate binary format security descriptors, as described below.

When considering folder permissions, it is important to remember that while the Exchange database looks like a file system when accessed via WebDAV, it does not behave like a filesystem internally. In particular, access to an object is controlled only by its own security descriptor; you do not need to have "folder visible" permission on an object's parent in order to be able to access the object.


SIDs and Security Descriptors

This information mostly comes from WinNT.h (though I've changed the code samples to use GNOME types rather than Windows ones).

A SID (Security IDentifier) looks like:

typedef struct {
       guint8  Revision;
       guint8  SubauthorityCount;
       guint8  IdentifierAuthority[6];
       guint32 Subauthority[];
} SID;

#define SID_REVISION 1

Revision is always SID_REVISION. IdentifierAuthority appears to be a 6-byte big-endian number. At any rate, the first 5 bytes are always 0. There are then SubauthorityCount 4-byte Subauthority values.

A user has a list of SIDs associated with them, including their own personal SID, the SID for "all users in the local domain", the SID for "Default", and SIDs for any Windows security groups that they are members of.

SIDs are also sometimes expressed in string form as: "S-r-i-s0-s1..." where "r" is the Revision, "i" is the IdentifierAuthority, and "sn" is the nth Subauthority.

The ntsecuritydescriptor property on an Exchange 2000 object always (?) starts with the bytes:

08 00 04 00 00 00 00 00

This does not appear to be part of the descriptor itself, but just some sort of Exchange-specific overhead. According to MAPI-L, the first two bytes are the length of the header data to be skipped.

The security descriptor header looks like:

typedef struct {
	guint8   Revision;
	guint8   Sbz1;
	guint16  Control;
	guint32  Owner;
	guint32  Group;
	guint32  Sacl;
	guint32  Dacl;
} SECURITY_DESCRIPTOR_RELATIVE;

#define SECURITY_DESCRIPTOR_REVISION 1

Revision is always SECURITY_DESCRIPTOR_REVISION. Sbz1 is zero padding. Control is a bitfield as described below. The remaining fields are offsets from the start of the header (ignoring the Exchange-specific data mentioned above). Missing sections (eg, the SACL) have 0 for their offset.

Control is a bitfield of:

#define SE_OWNER_DEFAULTED         0x0001
#define SE_GROUP_DEFAULTED         0x0002
#define SE_DACL_PRESENT            0x0004
#define SE_DACL_DEFAULTED          0x0008
#define SE_SACL_PRESENT            0x0010
#define SE_SACL_DEFAULTED          0x0020
#define SE_DACL_AUTO_INHERIT_REQ   0x0100
#define SE_SACL_AUTO_INHERIT_REQ   0x0200
#define SE_DACL_AUTO_INHERITED     0x0400
#define SE_SACL_AUTO_INHERITED     0x0800
#define SE_DACL_PROTECTED          0x1000
#define SE_SACL_PROTECTED          0x2000
#define SE_RM_CONTROL_VALID        0x4000
#define SE_SELF_RELATIVE           0x8000

Where (from WinNT.h):

SE_OWNER_DEFAULTED, SE_GROUP_DEFAULTED

The Owner / Group was provided by a defaulting mechanism rather than explicitly provided by the original provider of the security descriptor.

SE_DACL_PRESENT, SE_SACL_PRESENT

The Discretionary/Security ACL is present.

SE_DACL_DEFAULTED, SE_SACL_DEFAULTED

The DACL/SACL was provided by a defaulting mechanism rather than explicitly provided by the original provider of the security descriptor.

SE_SELF_RELATIVE

Always set when using this interface. Indicates that the SD occupies contiguous memory and contains offsets rather than pointers.

Outlook-generated descriptors usually have 0x8c04 (SE_SELF_RELATIVE, SE_SACL/DACL_AUTO_INHERITED, and SE_DACL_PRESENT).

Each ACL header looks like:

typedef struct {
	guint8  AclRevision;
	guint8  Sbz1;
	guint16 AclSize;
	guint16 AceCount;
	guint16 Sbz2;
} ACL;

#define ACL_REVISION     (2)
#define ACL_REVISION_DS  (4)

Outlook seems to always use ACL_REVISION for AclRevision, but the buggy case of setting an XML security descriptor uses ACL_REVISION_DS. WinNT.h says "The current AclRevision is defined to be ACL_REVISION." MSDN says "Use ACL_REVISION_DS if the ACL contains object-specific ACEs." We require AclRevision to be 2.

AclSize is the total size of the ACL including all of its ACEs.

Each ACE looks like:

typedef struct {
	guint8  AceType;
	guint8  AceFlags;
	guint16 AceSize;
	guint32 Mask;
	SID     Sid;
} ACE;

/* type */
#define ACCESS_ALLOWED_ACE_TYPE  (0x00)
#define ACCESS_DENIED_ACE_TYPE   (0x01)

/* flags */
#define OBJECT_INHERIT_ACE       (0x01)
#define CONTAINER_INHERIT_ACE    (0x02)
#define NO_PROPAGATE_INHERIT_ACE (0x04)
#define INHERIT_ONLY_ACE         (0x08)
#define INHERITED_ACE            (0x10)

If INHERIT_ONLY_ACE is NOT set, the ACE applies to the folder itself. If CONTAINER_INHERIT_ACE is set, it applies to subfolders. If OBJECT_INHERIT_ACE is set, it applies to subitems. In normal Outlook-generated ACEs, OBJECT_INHERIT_ACE and INHERIT_ONLY_ACE always go together, and our code requires that. (This means we can't parse the default ACL on /public though...)

INHERITED_ACE means the ACE was inherited from something. We don't care about, but we still have to keep track of it, because if it is set when you read the ACE, you have to set it when you write the ACE back (even if you changed the permissions), or Exchange will consider your ACE to be an additional ACE rather than a replacement, and then you'll end up with an ACL that Outlook doesn't like.

The Mask is essentially opaque to us. e2k-security-descriptor.c handles translating the MAPI permissions into sets of allowed and denied NT permissions according to rules from the WSS SDK.


Order of ACEs

The ACEs must appear in a specific order. This is at least partly determined by how security descriptors work, and how the Outlook permissions model works.

The way Windows checks an action against an ACL is something like this:

for each ACE in the ACL {
	if ((ACE.Mask covers the attempted_action) &&
	    (ACE.Sid is in CurrentUserSidList))
		return (ACE.AceType == ACCESS_ALLOWED_ACE);
}
return FALSE;

In Outlook's permissions model, there are three possibilities:

  • If the user is specified explicitly in the ACL, he gets exactly the rights he's specified to have, and nothing more

  • If the user isn't specified, but is a member of at least one group that is specified, he has the superset of all the rights granted to those groups.

  • Otherwise, the user has the rights granted to "Default"

So if you have:

Default

Editor

Bob

Reviewer

Bob&Jane

Contributor

Jane&Ted

delete items

then Bob has "Reviewer", Ted has "delete items", Jane has "Contributor" and "delete items", and everyone else has "Editor". To make that work in the Windows security model, we have to put the ACEs in a specific order, and add explicit "deny" ACEs to prevent fallthrough.

So, the ordering is:

Caution

While this is what we implement in e2k-security-descriptor.c, it is known to be wrong. Some folders' ACLs (especially public folders) don't obey this order. Further investigation is needed.

  1. Object-level ACEs for real users, in allow/deny pairs

  2. Container-level ACEs for real users, in allow/deny pairs

  3. Object-level ACEs for Anonymous, in allow/deny pairs

  4. Container-level ACEs for Anonymous, in allow/deny pairs

  5. Container-level ACEs for groups, first all the allows, then all denies

  6. Container-level ACEs for Default (allow only)

  7. Object-level ACEs for groups, first allows, then denies

  8. Object-level ACEs for Default (allow only)

"Default" is never explicitly denied anything.

The ordering above isn't explained completely by the preceding description (in particular, there doesn't seem to be any reason to want to put object- and container-level ACEs in a particular order). I'm not sure if there are additional unknown constraints that force exactly that ordering or if it's just an artifact of how Outlook creates them. At any rate, we do it the same way.