Evolution Connector for Microsoft Exchange Programmer’s Reference Manual |
---|
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-
"
where "r
-i
-s0
-s1
...r
" is the
Revision
, "i
" is the
IdentifierAuthority
, and
"sn
" is the n
th
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
):
|
The Owner / Group was provided by a defaulting mechanism rather than explicitly provided by the original provider of the security descriptor. |
|
The Discretionary/Security ACL is present. |
|
The DACL/SACL was provided by a defaulting mechanism rather than explicitly provided by the original provider of the security descriptor. |
|
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 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.
Object-level ACEs for real users, in allow/deny pairs
Container-level ACEs for real users, in allow/deny pairs
Object-level ACEs for Anonymous, in allow/deny pairs
Container-level ACEs for Anonymous, in allow/deny pairs
Container-level ACEs for groups, first all the allows, then all denies
Container-level ACEs for Default (allow only)
Object-level ACEs for groups, first allows, then denies
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.