The term Login/Logout script is fairly explanatory; a login script is simply an executable script which is ran immediately following the successful authentication of a user at an OS X GUI login window. In OS X, GUI authentication is handled by the loginwindow process, which upon logging in a user, has facilities to launch specific scripts as specified by it’s configuration. Likewise, upon logging out of a user session, the loginwindow process can call logout scripts at the end of the process. These scripts are often called hooks, due to the manner in which the script is caught by the login or logout processes.
Login and logout hooks are functionally identical, and are executed under uid 0. That is, they run with root privileges. In order to properly identify the user environment in which they are running, the system passes the user’s short name as the first argument to the script.
Hooks can be extremely useful in a number of scenarios. In one way or another, they are generally used to “prep” a user environment. Such preparations might include configuration of a particular program’s configuration, user environment optimizations, configuration changes, software installation, mounting of a network sharepoint, etc… Anything you can script, you can turn into a loginhook; you have access to the same tools that you would have in an OS X shell environment: Perl, Python, and bash scripts, which can pretty much do all of your bidding, short of double-bagging your groceries.
That being said, the majority of the environments that I feel are required candidate’s for loginhooks are operating with Network Home Directories. That is, the entirety of a user’s environment is loaded and accessed live across the network from any client station on the network. When a user with a network home directory logs into a computer, it looks and acts pretty much the same as if it were a standard OS X session, however, when they save a document to their Desktop or Documents folders, it’s actually being saved on a hard drive attached to a file server, rather than the hard drive in the computer in front of the user. I say that it acts “pretty much the same”, and there-in lies the rub. It’s not the same. If not planned properly, Network Home directories can be an absolutely crippling experience. Very rarely do people realize the burden that switching to a networkhome model bares on a fileserver. First and foremost, say you have 30 user’s on your network, all using local home directories. Now, because you heard Network Homes are cool (and they are), you want to implement them for all your users. Downtime due to problematic equipment is reduced, data can be better secured, and backups are easier to manage. You have a file server, so why not?
Well the problem is that your 3 drive Xserve running OD, AFP, Wiki’s, and Retrospect is a poor substitute for 30 individual hard drives currently being utilized by your client stations.
But I digress, this article assumes this is not you. This article assumes you are smart, that you have a good server distribution, planning for about 60-75 concurrent users per server, and a fast RAID backend (RAID 10 recommended). This article assumes you have a server and storage solution capable of sustaining your environment, but you just aren’t quite happy with the end user results: more beachballs, crashy programs, and general headaches.
If this is you, then loginhooks can definitely relieve some stress. The nature of Network Home Directories means that some programs will simply not function properly. For instance, programs which use a database for storage may find that network home directories are too underperforming to properly operate. Conversely, you may find that applications which deal with media files are detrimental to server disk performance, negatively impacting your user base; 30 simultaneous users editing movies in iMovie over your network probably isn’t going to provide a very good experience I’m afraid. Other programs may just be coded poorly and do not know how to deal with network home directory paths. Locking issues are also not uncommon. Older version’s of Firefox used an IP-encoded .parentlock file in ~/Library/Application Support/Firefox/Profiles that could cause locking issues when switching between computers. iPhoto also has it’s own locking data, stored at “~/Pictures/iPhoto\ Library/iPhotoLock.data”. If iPhoto launches and that file is present, it will complain about the lock and die. Not much fun for your end user’s, and something perfectly fixable through the deployment of loginhooks.
These examples are fairly typical of problems which you can negate through the implementation of loginhooks. The first two issues, performance, are the easiest ones to combat; if a program’s access patterns make it unstable or resource unfriendly when stored on a network home directory, why not store it on the local disk instead? For instance, you may have noticed that Adobe Reader 9 simply will not work on Network Home Directories. You launch the program and it dies shortly thereafter. Well, Reader stores it’s support data at ~/Library/Application Support/Acrobat/. What happens if we place this directory on local storage:
## list my current directory
helyx:~ hunterbj$ pwd
/Network/Servers/hax.lbc/Users/hunterbj
## remove the Acrobat folder
helyx:~ hunterbj$ rm -r ~/Library/Application\ Support/Adobe/Acrobat
## make a temp folder on the local drive and create a symlink
helyx:~ hunterbj$ mkdir /tmp/myacrobat/
helyx:~ hunterbj$ ln -s /tmp/myacrobat/ ~/Library/Application\ Support/Adobe/Acrobat
Now, if we open up Reader, the program miraculously works! This is due to the fact that when Reader tries to access it’s data at ~/Library/Application Support/Adobe/Acrobat/, which is in my home directory on the network, it is actually redirecting to the local drive’s temp folder at /tmp/myacrobat/. Acrobat is no longer using the network storage for it’s own data, and is now functional to boot! Redirection like this can be very handy, but the biggest caveat is that the data is now stored on the local drive, not the network. If the user moves to a different computer, that data will no longer be there: the folder /tmp/myacrobat won’t even exist on the next computer, and so the symlink will be broken, and in turn, Reader will be broken. Even if /tmp/myacrobat did exist, it would contain different data than the last computer. This isn’t a big deal for Acrobat, but lets say you were redirecting instead ~/Movies so that iMovie footage doesn’t crush your server. Now a user can only access those movies from the one computer. Similarly, you might be tempted to redirect “~/Documents/Microsoft User Data” or “~/Library/Mail”, but if your user’s use POP accounts, that could cause issues. This certainly can have implications that may not work in your environment, so plan carefully.
loginhooks!!! get yer loginhooks!!!
Well if you’re still reading, you might be saying to yourself “gee, these loginhooks sure sound nice, I wish I knew how to script!”, well, luckily we can help here. Attached you will find login and logout scripts which contain everything you will need to deploy loginscripts to your environment. Login and Logout Hook Examples
The attached loginhook script has numerous provisions for redirecting folders, and includes a few application-specific tweaks that can be used in your environment.
First and foremost, these scripts focus on local folder redirection, which will fix 90% of the problems that crop up in Network home directory environments. The script ships by default with a list of recommended folders to redirect, but you will want to examine your environment and redirect what fits best for you.
If you want to tweak which folders are redirected, you can open the attached script and search for the following lines (hint: it’s close to the top after the intro header)
## Specify our Redirects
## example: redirectDirs=”Library/Caches:deleteExisting,Library/Fonts:syncExisting”
redirectDirs=”Library/Caches:deleteExisting,Library/Fonts:syncExisting”
redirectDirs+=”,Movies:moveExisting,Pictures:moveExisting,Library/Mail:moveExisting”
redirectDirs+=”,Library/Printers:deleteExisting,Library/Mail:moveExisting”
redirectDirs+=”,Library/Application Support/Adobe/Acrobat:deleteExisting”
The basic syntax for an entry is: path/to/folder:action The path should be entered relative to the user’s home directory, and action can be one of the following:
a. ‘deleteExisting’ (the specified folder will be deleted)
b. ‘syncExisting’ (the specified folder will be renamed to “folder (network)”, and it’s contents will be copied to the local redirect folder)
c. ‘moveExisting’ (the specified folder will be renamed to “folder (network)”
Each redirection entry should be separated by a comma. For instance, if you wanted to redirect user’s Mail and Caches folders, you would have the following entries (and remove all others):
redirectDirs=”Library/Caches:deleteExisting,Library/Mail:moveExisting”
Please note, this is the same thing as:
redirectDirs=”Library/Caches:deleteExisting”
redirectDirs+=”,Library/Mail:moveExisting”
Each folder specified above will be redirected to a folder on the local drive. For instance, if user “testuser” logs in with the above command, they will have a local folder created at /Users/Local/testuser, which will mirror the user’s home directory, but will only contain items specified by your redirectDirs entries (in this example, ~/Library/Caches and ~/Library/Mail).
You can change /Users/Local to any directory of your choosing, simply modify the appropriate line at the top of the configuration file:
## Global folder where all local users cache data will reside
localDataDir=”/Users/Local”
The associated logouthook ensures that all specified redirects will be undone when a user logs out. For entries specifying the actions “moveExisting” or “syncExisting”, upon logout, the “folder (network)” folder will be renamed to “folder”. You do not need to specify any redirectDirs entries in the logouthook, it reads those in from a preference file. The only time you will need to modify logouthook is if you change your localDataDir entry from something other than /Users/Local
On top of this, the loginhook does some client-side tweaking, namely it has some provisions for fixing Firefox and iPhoto lock-file problems that can occur in network homedir environments. If you are hit by either of these bugs (you’ll know if you are), then the attached scripts can help with that. Simply open the loginhook.sh script, and look for the following section:
## Look for and clear iPhoto locks. set to 1 for true
declare -x -i iPhotoLockFix=0
## look for and clear FireFox locks. set to 1 for true
declare -x -i firefoxLockFix=0
Each of these application specific tweaks can help you to resolve these issues. To enable any of these fixes, simply change the =0 to =1, and then deploy it in workgroup manager.
Deploying loginhooks
You can deploy loginhooks a few different ways. Independently, it is possible to configure a loginhook locally for use by a single computer. Local configuration for loginhooks are stored in the file /Library/Preferences/com.apple.loginwindow.plist. You can use the defaults command to configure loginwindow to fire login and logout hooks:
sudo defaults write com.apple.loginwindow LoginHook /Library/Loginhooks/loginhook.sh
sudo defaults write com.apple.loginwindow LogoutHook /Library/Loginhooks/logouthook.sh
For the above to work properly, you will need to have created the folder /Library/Loginhooks, and saved the appropriate loginhooks in the folder. They will need to be executable to fire, you can use the chmod +x command for this:
chmod +x /Library/Loginhooks/*
Alternatively (and preferably), we can deploy login and logout hooks en mass to our clients using Open Directory and MCX. By selecting the “login” managed preference pane on any computer or computer group record in Workgroup Manager you will have access to a “Scripts” tab which provides you the ability to select both a login and a logout script to deploy to your computers. You can select any executable and then click save, which will encode the script in base64 and store it in Open Directory. If you’re having trouble selecting your script, the word executable is key here, if you’re loginscript is not executable, you won’t be able to select it from this interface. Also, make sure your script properly start wish a hash-bang statement (#!/bin/bash).
I would like to say that’s all there is to it, but unfortunately that’s not the case. The first problem is that, by default, an OS X client is set to ignore MCX login scripts. To turn these on on the client, run the following command:
defaults write /var/root/Library/preferences/com.apple.loginwindow EnableMCXLoginScripts -bool true
The second problem is that once again by default an OS X client will likely not trust MCX scripts deployed from your OD environment. In order for a client to do so, we must specify the trust level that a client needs to establish with an OD server in order to trust the scripts that it deploys. To specify this trust, we must first determine the type of OD bind that is being used for client machines. This can be done by running the following command on a client desktop:
dscl localhost read /LDAPv3/myserver.com dsAttrTypeStandard:TrustInformation
TrustInformation: Authenticated Encryption
In this example, the client is using an authenticated bind over SSL. To enable MCX scripts with this directory, we can run the following:
defaults write /var/root/Library/preferences/com.apple.loginwindow MCXScriptTrust -string Authenticated
In most environments untrusted binds are utilized and so the following command is most applicable:
defaults write /var/root/Library/preferences/com.apple.loginwindow MCXScriptTrust -string Anonymous
Here is a list of all of the trust levels and their details:
- FullTrust: The client will only trust scripts specified by Directory Servers to which the client has performed a trusted bind to. A FullTrust relationship also requires that the options to block man in the middle attacks, and Digitally sign every packet are checked.
- Authenticated: The client will trust a server only if it has successfully authenticated via a trusted bind.
- PartialTrust: Like a full trust, a partial trust requires a trusted bind. Packets here must also be Digitally signed. Active Directory bindings typically occur at this level.
- Encryption: The client will trust only servers supporting ldaps:// connections
- DHCP: The client will trust only servers specified in Option 95 of their active DHCP packet.
- Anonymous: The client will trust scripts configured in any configured directory server.
Why not use MCX Redirects?
You may be thinking that all of the above is a lot of work, when MCX Redirects can accomplish the same thing. Well, you can certainly use MCX redirects to accomplish folder redirection. However, in my experience, they are fairly limited. There’s no ownership testing, no path validation, and if you want to use a folder other than /tmp for your redirects, you’ll need to create that folder yourself on each of your clients, as the built in facilities won’t do any folder path creation (other than the top-level user folder).
Lastly, MCX redirects run in user-space, and by default, an OS X home directory has ACL deny entries that prevent user’s from deleting ~/Movies for instance. These ACL’s will interfere with MCX redirects. Lastly, the “deleteExisting” option is really the only reliable MCX redirect function, it’s facilities to rename/replace are pretty lacking in my opinion. Additionally, the MCX teardown process has no facilities to detect simultaneous user logins, and as such could tear down an environment that is actively in use on another computer. The attached scripts have provisions to specifically address all of these issues. On top of this, MCX Redirects are only supported by 10.5 and later, while the provided script works with 10.4-10.6. Of these OS’s, 10.4 is the least efficient in terms of Network Home Directory usage, and is therefore a more ideal candidate for folder redirection (specifically ~/Library/Caches). Therefore, if you have a lot of 10.4 clients, loginscript solutions such as this are really your only option.
There’s really only one “gotcha” to this setup, which would be true of MCX redirects as well: If a user logs into one computer which been configured for redirects, and then, prior to logging out, logs into a machine that is not, then the user environment will be a little wonky: the symlinks to redirect to local storage will likely be in the user’s home directory, but the local redirect data will not be on the local drive, this will result in broken functionality on that client. Symptoms could include heavy beachballing and application instability (highly dependent on the application).
Moral of the story: user’s will need to ensure that they logout prior to logging into an older machine (Apple Menu->shutdown is fine), and your admins should ensure that all machines running network home directories are properly configured.