I personally don’t think it’s a good thing to blog in english when you’re french, unless you are very fluent and your target audience reads english. Today, my audience is the worldwide crowd of Mac OS X Server sysadmin. So, while I’m not fluent, I’m going to write my first post in english.
There is something quite messy in the Service Access Control Lists (SACLs) on Mac OS X 10.5: you just can’t display the full users & groups list of a SACL in command line.
Basically, you can do this:
$ dscl . -read /Groups/com.apple.access_ssh AppleMetaNodeLocation: /Local/Default GeneratedUID: A7E16606-3C52-42B9-852E-D197C7598EA8 NestedGroups: 955F946A-7C9D-4D3E-B286-E16003380282 ABCDEFAB-CDEF-ABCD-EFAB-CD... PrimaryGroupID: 101 RealName: Remote Login Group RecordName: com.apple.access_ssh RecordType: dsRecTypeStandard:Groups
As you can see, this SACL group
com.apple.access_ssh has no direct members, only nested groups (
NestedGroups key). So, in order to list users, you have to read the content of each nested group. But groups are only available by their name. So the first step is to find out group’s names.
At this stage, you have no way to know if the target group is local or if it sits on a remote open directory server, so you must use the
$ dscl /Search -search /Groups GeneratedUID 955F946A-7C9D-4D3E-B286-E16003380282 myadmins GeneratedUID = ( "955F946A-7C9D-4D3E-B286-E16003380282" )
The second step is to list users of the group:
$ dscl /Search -read /Groups/myadmins GroupMembership GroupMembership: admin01 admin02 user01 user02 ldapuser01
But guess what: this group might have more than just users, may be its
NestedGroups key is not empty! So at this point, you must also check the
NestedGroups value, and recursively follow each group
GUID, until you find only users.
Think "huge groups", think "handfulls of nested groups", and watch your fingers as you’re going thru
dscl torments. You’ve figured it out: Mac OS X lacks a good command line tool for following a SACL tree of users and groups.
Here come’s getsacls.sh
I won’t promise you a killer command line tool with foolproof error and recursion handling, but I still believe I’ve designed a usable piece of shell script. Even if it looks like it’s the worst code I’ve ever wrote (wich is not true, I’ve made things way uglier).
The source code is too long and messy to be just copy-pasted here, just follow this link to download the getsacls.sh script.
How to get
Just download the latest version from here.
How to install
Simply copy to your Mac OS X 10.5 server (or managed client). Somewhere in your
$PATH should be fine. Then
chmod +x the script, so that it can be executed.
How to configure
Defaults values should be ok, but if you really want to change something, open the script in your favorite editor, and find the "FEW USER TUNABLE MISCS" section. Edit at your own risks.
How to use
It’s simple, you just have to launch it. It will then proceed with the parsing of every SACL on your local system.
DO NOT use the
sh command to launch this script.
getsacls.sh uses special escape sequences and command options that
sh will not recognize. Just run:
If you want to parse only some SACLs, you can provide each SACL name at the command line:
$ getsacls.sh com.apple.access_ssh com.apple.access_loginwindow
Still, you should only use SACL names that exist on your local system.
The default output is "fancy", it uses bold, indentation, and a beach-ball cursor. If you want the "no fancy" mode, you can either edit the corresponding "tunable misc variable" or define
FANCY=NO at launch time:
$ FANCY=NO getsacls.sh com.apple.access_ssh
This "no fancy" mode allows for later parsing.
The script will not handle circular references. If your SACL uses nested groups in a circular way (group 1 -> group 2 -> group 1), the script will not stop.
When finding two or more similar users or groups (for example the local admin group and the open directory admin group), it will use only one of them, and that should be the local one.
The script uses SQLite3 as a backend, because bash is not good with arrays, and because I’m not good with PERL/Python/Ruby.
Sample "fancy" output:
com.apple.access_ssh -------------------------------- myadmins /LDAPv3/192.168.128.34 955F946A-7C9D-4D3E-B286-... admin01 /Local/Default 9A7917D1-D8E7-49D6-8211-... admin02 /Local/Default 40D516A2-4D02-4C92-9505-... ldapuser01 /LDAPv3/ldap.example.com ldapuser01_OUT_OF_OD ldapuser02 /LDAPv3/ldap.example.com ldapuser02_OUT_OF_OD ldapuser03 /LDAPv3/ldap.example.com ldapuser03_OUT_OF_OD user01 /LDAPv3/192.168.128.34 49EF9C64-D98B-11D8-BCFA-... admin /Local/Default ABCDEFAB-CDEF-ABCD-EFAB-... root /Local/Default FFFFEEEE-DDDD-CCCC-BBBB-... admin01 /Local/Default 9A7917D1-D8E7-49D6-8211-... admin02 /Local/Default 40D516A2-4D02-4C92-9505-... user01 /LDAPv3/192.168.128.34 49EF9C64-D98B-11D8-BCFA-... ================================
Sample "no fancy" output:
com.apple.access_ssh -------------------------------- g 1 myadmins /LDAPv3/192.168.128.34 955F946A-7C9D-4D3E-B286-... u 2 admin01 /Local/Default 9A7917D1-D8E7-49D6-8211-... u 2 admin02 /Local/Default 40D516A2-4D02-4C92-9505-... u 2 ldapuser01 /LDAPv3/ldap.example.com ldapuser01_OUT_OF_OD u 2 ldapuser02 /LDAPv3/ldap.example.com ldapuser02_OUT_OF_OD u 2 ldapuser03 /LDAPv3/ldap.example.com ldapuser03_OUT_OF_OD u 2 user01 /LDAPv3/192.168.128.34 49EF9C64-D98B-11D8-BCFA-... g 1 admin /Local/Default ABCDEFAB-CDEF-ABCD-EFAB-... u 2 root /Local/Default FFFFEEEE-DDDD-CCCC-BBBB-... u 2 admin01 /Local/Default 9A7917D1-D8E7-49D6-8211-... u 2 admin02 /Local/Default 40D516A2-4D02-4C92-9505-... u 2 user01 /LDAPv3/192.168.128.34 49EF9C64-D98B-11D8-BCFA-... ================================
As of now, current version of
getsacls.sh is 407 ($Id: getsacls.sh 407 2009-07-09 09:36:26Z patpro $). Next revisions will be listed here.
Update: $Id: getsacls.sh 409 2009-07-09 14:30:01Z patpro $
I’ve added some error handling for a rare case: when a user account lives on a LDAP server distinct from the Open Directory server, the
GroupMembership field is not updated on the OD if the user account is destroyed on the LDAP. So according to the
GroupMembership the user is still here, but according to the LDAP the user is nowhere to be found.
Update: $Id: getsacls.sh 412 2009-07-23 20:24:54Z patpro $
LC_NUMERIC in the beachball function, so that
sleep 0.05 runs as expected even for people not using the dot as a decimal separator. Some cleanup.
Update: $Id: getsacls.sh 414 2009-08-03 10:33:30Z patpro $
Some cleanup and english corrections. Added some delay to the beatchball rotation so it’s more enjoyable.
Feel free to comment, and to correct my english ;)