Wednesday, July 8, 2009

Writing loginwindow plugins

Writing loginwindow plugins

To solve a login problem at a former employer I needed to write some code that would be run really early in the login cycle[1]. It needed to be run after the user had authenticated through LDAP/Kerberos but well before loginhooks or LaunchAgents. As I played with things I found that I needed to actually run as part of the authentication process as the MCXCompositor was part of the problem[2].

In order to have my code run early enough to beat the MCXCompositor I actually needed to write my own loginwindow plugin. Apple's documentation calls these AuthorizationPlugins as their main job is to authenticate users. Smart-card vendors use AuthorizationPlugins to allow their hardware to help authenticate users. Correspondingly AuthorizationPlugins are low-level code and not for the faint of heart. Apple does provide documentation, NullAuthPlugin sample code, and something of an introduction, but it is still a difficult topic to get started with.

Having gone through the process once, I thought I should document the basics so that others could be spared some of the rough edges. This should be a really rare requirement, but it might pop up in odd places.

Introduction to AuthorizationPlugins

AuthorizationPlugins come in two flavors: privileged and non-privileged. The difference is that privileged plugins run as root, but do not have access to the WindowServer (and so can't display a GUI), whereas the non-privileged versions are allowed to display a GUI, but are not run as root. The privileged ones should probably be written in straight C code (so essentially Carbon) as Apple does not recommend using Foundation or AppKit for processes that run as root. This recommendation is not enforced anywhere, but be cautious in violating it.

As the name implies AuthorizationPlugins are actually run as plugins for the loginwindow process. This does make it a little difficult to test your plugin, so you will want a second computer that you can ssh into to test your plugin. Working with a single computer is an recipe for problems. This also complicates getting the results of your tests back, since you can't use printf statements, or any of the other usual debugging tools. Instead I would recommend using the ASL library to send messages to the system log.

The same compiled executable can actually serve multiple roles, and be called multiple times during a single authorization request. The loginwindow will call the MechanismCreate function for each listing in the "system.login.console" section of the /etc/authorization file. Each entry has this format (but with no spaces):

<plugin-name> : <mechanism-name> , privileged

<plugin-name> is the name of your bundle that you put in /Library/Security/SecurityAgentPlugins (minus the '.bundle'), but <mechanism-name> is an arbitrary string that gets passed to your MechanismCreate function. You can use this to provide different behaviors from your plugin. This could allow you to have a single bundle that has both privileged and GUI components to it, or have two different modes in your plugin. The final 'privileged' (and its preceding comma) denotes that the plugin should be used in the privileged login host. If you omit it your plugin will be run in the GUI capable host.

Note that while the AuthorizationPlugin system allows for you to setup something like having multiple mechanisms in the same code, but it does not really provide for them directly. It will always call the same functions that you provide it in AuthorizationPluginCreate. However, you can have it pass pointers to data structures that your provide depending on the Mechanism at work, with that is is possible to simulate having multiple mechanisms.

Ultimately the goal of an AuthorizationPlugin is to give the loginwindow a response to the implicit question that it is asking: "should this user be allowed to login with the information they have given". So at the end your plugin's main method you have to give a response. The choices are: allow, deny, the user canceled, and undefined (internal failure). All of the plugins listed in appropriate section in /etc/authorization are called in order, and the first one to say "no" or "cancel" will stop the process.

In my particular case I was not actually validating the user login, so the plugin I worked on always answered "yes" (kAuthorizationResultAllow).

Data Structures

There are two data structures that AuthorizationPlugins allows you to pass through it from the creation methods your provide to the rest of your methods: AuthorizationPluginRef and AuthorizationMechanismRef. Each is provided to a set of your functions, the AuthorizationPluginRef pointer is passed to the MechanismCreate (where you set the AuthorizationPluginRef) and PluginDestroy methods, and all the rest are passed the appropriate AuthorizationMechanismRef. The pointers don't actually define the structure that they point at, so you get to define what the structures look like. This allows you to add in what is needed by your plugin, so you can include any resources that your plugin might need to do its job. But you do have the responsibility to both malloc the memory for these items, and free them when you are done. And when free'ing them you have make sure you don't double-free them (especially the case if you wind up with multiple Mechanisms).

There are two things that are passed to your 'Create methods that you need to pass along to your other methods through your data structures [3]: In AuthorizationPluginCreate you are passed the AuthorizationCallbacks pointer. This pointer is required by other parts of your plugin, but your are only passed the reference to it here. And the AuthorizationEngineRef is passed to the MechanismCreate method, and is a required augment in the call to get data like the username that the user typed (accessed through the AuthorizationCallbacks pointer). So even if you have no other information to pass, you will need to create and manage these structures.

As was covered before loginwindow provides you with some support to simulate having multiple "instances" of your method. It does this by calling the MechanismCreate method multiple times, each time with a different AuthorizationMechanismId. Depending on the AuthorizationMechanismId you are passed in this method (it is the string from ) you can choose to provide a different AuthorizationMechanismRef instance that will be provided to your future methods when that "instance" is called for. It is not exactly object-oriented coding, but can be used to simulate having actual objects.

Additionally there is one pre-defined data structure that you are going to need to malloc space for, fill in, and then subsequently free when it is no longer needed: the AuthorizationPluginInterface structure. This is a struct with 6 items in it: a version number (so that Apple can change the system later without breaking older plugins), and pointers to your five callback functions. Note that since we are in straight-c you will need to malloc the space for this structure (so the compiler does not automatically reclaim it at the end of this function), and then take care of freeing this space when cleaning up the plugin.

The required methods

Only one of your method names is set in stone (AuthorizationPluginCreate), but you are required to have methods for all six methods with corresponding signatures, and you then point the loginwindow at these methods with a structure you define for it(AuthorizationPluginInterface). So you can name the other five methods after AuthorizationPluginCreate whatever you would like, but the AuthorizationPluginInterface must point at them.

AuthorizationPluginCreate

This is the only method whose name is required to be the Apple-supplied one, and it serves two purposes. The first is to set a structure (an AuthorizationCallbacks struct) that tells the loginwindow the addresses of the functions you want it to call, and the second is to arrange access to the functions that the loginwindow provides to the rest of your code. Unfortunately the pointer used to access this is passed to the AuthorizationPluginCreate method, but not to the rest of the methods[3]. As was discussed in the Data Structures section you need to take responsibility for mallocing and freeing this chunk of memory so that it does not go out of scope, but gets release when not needed (presumably in PluginDestroy).

As discussed in the Data Structures section you will need to malloc space for the AuthorizationPluginInterface structure that you will be passing to the loginwindow. This is what tells the loginwindow where the rest of your methods are, so the space needs to be managed for the duration of the plugin's life. You will also need to fill in this structure in this method, so that the loginwindow can find the rest of your methods.

This function is also a good place to startup any processes that might be needed by every call to your plugin and can be shared between instances, for example ldap connections. Resources like this can either be shared with the rest of your code through the same methods that you use to pass the loginwindow functions pointer. At present the loginwindow process dies off when a person logs in taking all of its plugins with it, and a new process is created when you return to the loginwindow. But I would not depend on this behavior.

The return code from this method (and all other subsequent methods) should be one of two values: errAuthorizationSuccess if nothing has gone wrong, and errAuthorizationInternal if something has failed.

MechanismCreate

The goal in this function is to setup your "Mechanism". As was discussed in the introductory section the loginwindow provides you with the ability to simulate having multiple Mechanisms in your code, but leaves the actual implementation of this to you. In this method you are given two tools to do this with: the name of the "Mechanism" as provided in the /etc/authorization database, and the space to put a pointer for your AuthorizationMechanismRef. The former allows you to figure out what Mechanism you should be setting up, while the latter is the place to put the data or resources that the rest of your plugin will need to do its work.

Even if you don't think that you will ever be using multiple mechanisms in your code it might be a good idea to setup your MechanismCreate and MechanismDestroy methods as if you were. This is because this code will be invoked with whatever text is put into /etc/authorization, so a system admin (say the one in the position after you) may accidentally put two calls to the code, or the typo of the "proper" Mechanism name. If don't setup for this possibility your code might error out during the login process, and render the computer unable to login. Safe is better than sorry in this case. So it might be tempting to use global variables for some of the resources, but if you do bow to temptation, make sure that you are properly managing those resources with the possibility of multiple mechanisms being in play.

When creating the data structure that you will be passing to the other methods that use your AuthorizationMechanismRef you should provide room for at least two things: the AuthorizationCallbacks pointer that you passed yourself using the AuthorizationPluginRef and the AuthorizationEngineRef that is passed to this method. Other Mechanism methods will need at least one of the functions accessible through the AuthorizationCallbacks pointer, and will need to provide the AuthorizationEngineRef to this call. So even if you will not otherwise need to pass anything through the AuthorizationMechanismRef, you still need to use it. Remember to malloc the space for the structure you create for your AuthorizationMechanismRef.

As with all other methods, your code should return either errAuthorizationSuccess or errAuthorizationInternal.

MechanismInvoke

This method is the loginwindow implicitly asking your code the question: "should the user be allowed to continue to login" and so should be the meat of your method. You answer this question by calling the loignwindow's SetResult function. To do this you need to access this function through the the AuthorizationCallbacks pointer that you got in AuthorizationPluginCreate and provide it with the AuthorizationEngineRef that you got in MechanismCreate. This is the one reason that you absolutely need to use setup a structure to pass as a AuthorizationMechanismRef for every time your MechanismCreate method is called (since the AuthorizationEngineRef is probably different).

The call itself looks something like this (you need to put in your variables here): mechanismRef->authCallBacks->SetResult(mechanismRef->authEngine, kAuthorizationResultAllow)

Where authCallBack is the AuthorizationCallbacks that your AuthorizationPluginCreate method received, mechanismRef is the AuthorizationMechanismRef received by this method, and authEngine is the AuthorizationEngineRef received by your MechanismCreate method.

For more advanced plugins, you might not call SetResult before returning from this function, but rather call it from a thread that is started in this method. you would have to provide this other thread with the handles needed to respond to the loginwindow, and then respond when you have finished processing (or asking the user a question). This allows other plugins to do their work while you are waiting for your answer to be ready.

As with all other methods, your code should return either errAuthorizationSuccess or errAuthorizationInternal.

MechanismDeactivate

This method is mostly involved in situations where a plugin has multiple Methods and at least one of them leaves a thread running to interact with the user. However, even if your plugin is much simpler than this, you need to implement this method and in it call the callback method DidDeactivate to tell the loginwindow that you are back to waiting for another authorization attempt. For a simple Mechanism it could be something like this:

static OSStatus MechanismDeactivate(AuthorizationMechanismRef mechanismRef) {
    return ((MechanismRecord *) mechanismRef)->authCallBacks->DidDeactivate(((MechanismRecord *) mechanismRef)->authEngine);
}

Where the same assumptions from MechanismInvoke are used with the addition that MechanismRecord is the type you have declared that AuthorizationMechanismRef points at.

As with all other methods, your code should return either errAuthorizationSuccess or errAuthorizationInternal, but the call to DidDeactivate should take care of that for you.

MechanismDestroy

When the loginwindow starts to close (either at successful login or shutdown/restart) it gives you a chance to wind-down your plugin and release any resources that you might have (memory or connections). MechanismDestroy is the first step, and it is called once for every Mechanism that has been started for your plugin providing the appropriate AuthorizationMechanismRef for each. At a minimum you should free() the memory that you allocated for this AuthorizationMechanismRef.

As with all the other methods you need to reply back to the loginwindow with either errAuthorizationSuccess, or in case of an error errAuthorizationInternal.

PluginDestroy

This is the final step in closing down for your plugin, giving you a chance to clean up any plugin-wide resources. If you have malloced any memory (global variables), or spun off any other threads that were not taken care of by the MechanismDestroy methods, now is the time to take care of them. At minimum you are going to need to free() the space you malloced for the AuthorizationPluginInterface that you pointed the loginwindow at in your AuthorizationPluginCreate function. You are passed you the same pointer that you gave in your AuthorizationPluginCreate method to pass to your MechanismCreate method, so you should have easy access to those resources.

As with all the other methods you need to reply back to the loginwindow with either errAuthorizationSuccess, or in case of an error errAuthorizationInternal.

Footnotes

[1] The specific reason is that a program (aklog) needed to be run as the user before the home directory (stored on AFS) would be available for use. And I discovered that a few of Apple's processes could run (the MCX compositor, the font cacher, and the Dock are prime examples). If I did not do this then the login would take more than a minute and a number of things would fail and some user's preferences files would be overwritten.

[2] The MCXCompositor actually is run as a AuthorizationPlugin as it provides the opportunity for the MCX settings to veto the login. The entry that runs it can be found at /etc/authorization along the path: rights/system.login.console/mechanisms. It is the "MCXMechanism:login" entry.

[3] My opinion is that the required items should be passed to each step in the process by the loginwindow and not require the plugins to pass this information themselves, through the loginwindow. You can avoid this somewhat by issuing global variables, but that is a bit inelegant and tends to violate the principals of encapsulation. I hope that when Apple next revisits this area of code they re-think that particular bit os silliness.

Friday, June 19, 2009

Looking for a job

On a more personal note: I recently was laid off from my job as part of a economy-related layoff at my employer, and so I am in the market for a job.

My work supporting large-scale Macintosh deployments has lead me into a lot of exciting areas in the last year: developing a loginwindow plugin, getting a patch accepted into Radmind, writing a multi-partition flexible imaging system, and taking over development of InstaDMG. Combined with presentations at two major conferences, MacWorld and SIGUCCS, it has been an exciting year, and I am looking for a position where I can continue this work.

I am open to both contract and full-time opportunities, and am willing to relocate for a good opportunity. If you have such a position available, or know of one that is coming available I would appreciate it if you could contact me at larkost@softhome.net. I am especially open to contracts for training or consultation on InstaDMG.

My resume can be found through these linkes, either in PDF format, or in Microsoft Word format. I am working to provide an open-source portfolio of my work, the start of which can be found at the following locations:

Saturday, June 13, 2009

Reindexing Apple Audio Loops

Reindexing Audio Loops

A number of Apple's products use Audio loops, most notably FinalCut Studio and GarageBand, but a few other applications make use of those loops as well. Those loops live in /Library/Audio/Audio Loops, and in order to be used need to be indexed (the indexes go in Library/Audio/Audio Loops Index). On most computers this is taken care of for you by the respective installers, but for computers that are imaged you usually don't run the installers on those computers. This is especially true if you use Radmind to manage the computers and have separate transcripts for the different Apps, or have selected computers that have additional loops purchased for them.

The solution for this is to use the same technique that Apple's installers use to index the loops. This article will outline a way of doing this. Note that this is similar to code that I have used in a previous job, but has not actually been tested in production use.

Grabbing the Indexing Tool

GarageBand and FinalCutStudio both have the ability to re-index the loops from within the App. From GarageBand you just drag the loops folder that you want to index into the loops area. This requires that the user be an admin, so is not a good solution for computer labs. Unfortunately this built-in ability is not scriptable (in a way that I have yet found). But there is a solution available: you can grab the tool that Apple's installers use from the latest installer. As of this point that is from the iLife '09 installer, and from the Installer DVD the application you need is:

iLife '09.mpkg/Contents/Installers/GarageBand.mpkg/Contents/Installers/GarageBand_Loops.pkg/Contents/Resources/ALPIndex.app

You will need to copy this .app bundle to a known location on all of your client computers (or keep it on a network share).

The script

We need a script to run that ALPIndex tool on all of the individual folders. If there are a lot of un-indexed loops to process the script can take some time, but if there is nothing new then it will complete within a second or so. My recommendation is to run the script on a regular basis. If you are using Radmind and have provisions for post-radmind scripts this is a good candidate for that.

Note that code assumes that you put the ALPIndexer.app in a folder at /Library/Management/ReindexAudioLoops. You should change that to match the setup on your system. If you are using the launchd option below, it assumes that you name this script reindexAudioLoops.bash and put it in this folder.

#!/bin/bash

ALP_INDEXER_PATH='/Library/Management/ReindexAudioLoops/ALPIndex.app'
ALP_PROGRAM_PATH="$ALP_INDEXER_PATH/Contents/MacOS/ALPIndex"
LOOPS_FOLDER='/Library/Audio/Apple Loops'
SUCEEDED=true

if [ `whoami` != 'root' ]; then
    echo "This tool must be run as root!" >&2
    exit 1
fi

echo "Reindexing Audio Loops: `date`"

IFS=$'\n'
for VENDOR_FOLDER in `/bin/ls "$LOOPS_FOLDER"`; do
 echo "Group: $VENDOR_FOLDER"
 for PROGRAM_FOLDER in `ls "$LOOPS_FOLDER/$VENDOR_FOLDER"`; do
  echo "  Processing: $PROGRAM_FOLDER"
  $ALP_PROGRAM_PATH "$LOOPS_FOLDER/$VENDOR_FOLDER/$PROGRAM_FOLDER" 2>/dev/null >/dev/null
  if [ $? == 0 ]; then
   echo "      Sucess"
  else
   echo "FAILED!"
   SUCEEDED=false
  fi
 done
done

echo "" # provide a space

# provide a summary of what is done
$ALP_PROGRAM_PATH -p

if [ $SUCEEDED == true ]; then
    exit 0
else
    exit 2
fi

Launchd item

If you are not using Radmind, then using launchd might be a good choice for running this script. A simple example is presented below. Placing this in /Library/LaunchDaemons with the appropriate permissions should work.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>Label</key>
 <string>info.larkost.reindexAudioLoops</string>
 <key>Program</key>
 <string>/Library/Management/ReindexAudioLoops/reindexAudioLoops.bash</string>
 <key>RunAtLoad</key>
 <true/>
</dict>
</plist>

Thursday, May 7, 2009

Run at Print

Running a script when a user prints

Some organizations (particularly schools and universities) require users to go to a web page to authenticate print jobs before those jobs can actually be printed. However, there is no documented way of automatically directing a user to a web page after they have printed. This article will demonstrate one way of doing this.

Warnings

This setup is a huge hack, and it is very likely that any system update that involves printers would overwrite it (requiring the hack to be re-installed). So this solution will best work on groups of computers that a system admin has complete control over. That said it also seems to currently be the best way of having a script run when something is printed.

Overview

Unfortunately the version of the printing system that comes with MacOS 10.5 (CUPS) does not have any obvious places to plug in to to get the behavior that we want, but there are a few places where we can hack into it to get the behavior we want. CUPS has a series of filters that print jobs get sent through to apply various transformations to the job to convert it to a format that the printer can understand. By causing the print job to take a little detour along this chain of filters we can capture the information we want, and still send the job on its way. The only filter that we can be sure that every print job goes through is the pstops (Postscript-to-Postscript) filter. In fact the CUPS system uses this filter to do a number of things, including print accounting and selecting page ranges.

But intercepting the job is just one part of what we need to do. Because the print filters run as root, we have to hand off a message to another process running as the user to act upon. Using a launchd item and a watched folder makes this process relatively easy. Our replacement filter will create empty files specific directory as a signal, and the launchd item will start a user-level process that will do most of the work and then erase the signal file.

Replacing the filter

To intercept the print job we have to move aside the pstops filter, and put our own filter in its place. But since we still want the functionality that pstops provides, we will call the normal pstops filter from within our replacement. The steps to do all this:

  1. Open Terminal.app and the go to the filters folder with:

    cd /usr/libexec/cups/filter

  2. Change the name of the original pstops filter to pstops.binary with

    sudo mv pstops pstops.binary

    Note that this will ask you for your password (you have to be logged in as an admin)

  3. Create our replacement item. You can do this a number of methods, to do it with pico in the teminal:

    sudo pico pstops

    copy-and-paste the following:

    #!/bin/bash
    WATCHED_FOLDER='/var/db/runAtPrint'
    
    # place the signal file, and make sure it can be deleted by the user-level process
    #	note: $PRINTER is a environmental variable set by CUPS
    /usr/bin/touch "$PRINTER_FOLDER/$PRINTER"
    /bin/chmod 0666 "$PRINTER_FOLDER/$PRINTER"
    
    # run the original pstops binary with the same input we were given
    exec $0.binary "$@"
    

    Then use <control>-x to exit pico, answer "y" when it asks to save, and use to accept the default name.

  4. Give our replacment filter the proper permissions with:

    sudo chmod 0555 pstops

    The owner and group should already be correct (root:wheel)

Creating the signal folder

The folder that we will use to transfer signals betwen the filter and the user-level process needs to exist before either side of our our process will work, so we create it ahead of time:

  1. Create the folder with:

    sudo mkdir /var/db/runAtPrint

    If you want to choose a differnt folder remember to change it in the pstops wrapper script, and in the launchd item in the next section.

  2. Make sure that it has the proper permissions so that both the filter and the user-level process can create/destroy itmes here with:

    sudo chmod 0777 /var/db/runAtPrint

Create the user-level script

The user-level script is what will pick up the signal files left by the filter, make sure that they are for printers on the proper server, and then call the user's choice of web browser. This part of the solution should probobably be cusomized to the indvidual institution, so this script is more of a reference implimentation. We are going to place the script into /Library/Scripts but in practice this script should go into the same place as any other management scripts your setup might have (the author usually create a folder at /Library/Management for this sort of stuff and a "Scripts" folder in that).

This reference script will ignore print jobs that go to printers that are not lpd queues on the server "printing.macenterprise-example.com" (a domain that does not exist), so at a bare minimum you will need to change the lines starting with "printingServer =" and "printingPage =" to reflect your environment. Any real use of this system will probably require at least a little more cusomization than that.

  1. Use pico in Terminal.app to create the script file:

    sudo pico /Library/Scripts/runAtPrint.py

    copy-and-paste the following:

    #!/usr/bin/env python
    
    import re, os, time
    
    ''' This watches the printer folder, and whenever there is an appropriate item
    	there launches a browser at the right page '''
    
    printerFolder		= '/var/db/runAtPrint'
    printingServer		= 'printing.macenterprise-example.com'
    printingPage		= 'https://printing.macenterprise-example.com/printer.php?printer=' 
    
    busyTimeout		= 10 # wait no more than this number of seconds 
    
    lpstatRegex		= re.compile('^device for (?P<printerName>\S+): lpd://(?P<server>\S+?)/(?P<queueName>\S+)')
    printerBusyRegex	= re.compile('^printer \S+ now printing')
    
    # setup a list of printers that we will watch
    watchedPrinters		= {}
    for thisPrinter in os.popen('/usr/bin/lpstat -s'):
    	result = lpstatRegex.search(thisPrinter)
    if result and result.group("server") == printingServer:
    	watchedPrinters[result.group("printerName")] = result.group("queueName")
    
    # process the files in the watched directory
    for thisPrinter in os.listdir(printerFolder):
    	if thisPrinter in watchedPrinters:
    		printerIsBusy = True
    		timeout = time.time() + busyTimeout
    		
    		# wait until the print server is done spooling
    		while printerBusyRegex.search( os.popen('/usr/bin/lpstat -p ' + thisPrinter).read() ) and time.time() < timeout:
    			time.sleep(.25)
    		
    		# open a new window in the user's browser to the right page
    		os.system('/usr/bin/open %s%s' % (printingPage, watchedPrinters[thisPrinter]))
    	
    	# remove the file even if it was not a printer we mange
    	os.unlink( os.path.join(printerFolder, thisPrinter) )
    
    os._exit(0)
    

    Then use <control>-x to exit pico, answer "y" when it asks to save, and use to accept the default name.

  2. Make sure that the script has the proper permissions:

    sudo chmod 0755 /Library/Scripts/runAtPrint.py

Create the launchd item

We want the user-level script to fire off every time our filter places something in the /var/db/runAtPrint folder, and Apple's Launchd system provides a nice facility to do exactly that. But we need to give it a plist to set this up. There are a number of ways of doing this, and ususally the author would use Property List Editor.app to create the file, but for simplicitie's sake we will continue to use pico to do so:

  1. Use pico in Terminal.app to create the launchd plist:

    sudo pico /Library/LaunchAgents/org.macenterprise.runAtPrint.plist

    copy-and-paste the following:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
    	<key>Label</key>
    	<string>org.macenterprise.runAtPrint</string>
    	<key>Program</key>
    	<string>/Library/Scripts/runAtPrint.py</string>
    	<key>RunAtLoad</key>
    	<false/>
    	<key>QueueDirectories</key>
    	<array>
    		<string>/var/db/runAtPrint</string>
    	</array>
    </dict>
    </plist>
    

    Then use <control>-x to exit pico, answer "y" when it asks to save, and use to accept the default name.

    Note: if you decide to change the name of this file, make sure that you keep the "Label" item in the plist in sync with the name. Launchd can get a little difficult to work with if the names to not match.

  2. Make sure that the launchd item has the proper permissions:

    sudo chmod 0644 /Library/LaunchAgents/org.macenterprise.runAtPrint.plist

    It should already have the proper group and owner (root:wheel).

Final Notes

At this point you should be able to log out, then back in, and if you did everything correctly the system should be running. As was mentioned in the Caution section, any Apple system update that changes printing has a chance of replacing the pstops file. In that case you would just need to move the new version to replace the old one you moved asside, and put the replacement script in its place again.

In case you missed it in that section: the user-level script does need to be customized to the environment that it will be running in. The reference script should be a good starting point for that customization.

Wednesday, April 15, 2009

Getting the ByHost string

Some preferences are per-computer, and Apple stores these preferences in "ByHost" folders in both /Library/Preferences and ~/Library/Preferences. Additionally a string that is supposed to uniquely identify the computer is added to the "preference domain" (the application's identification string in reverse-DNS notation). So the ByHost preferences for iTunes on my computer is named "com.apple.iTunes.776E2F18-DBC3-5F9E-BF52-C003E6E6C160.plist". Finding the bit that goes before the ".plist" is the real trick, and the subject of this post.
Most programmers will ever need to get this information as they don't have anything that should or needs to be stored on a by-host basis. Most of the rest can use the CFPreferences API's to do so (by supplying the kCFPreferencesCurrentHost constant), thereby always having the right information.
However, system administrators sometimes need to manage this information. For simple key-value items that are in the root of the given plist this is easily covered by using the '-currentHost' flag with 'defaults' command. But if the plist you are managing has any arrays or dictionaries in it that you want to touch you are probably stuck needing the value.
Apple has already changed this system once: at one point the value you needed to use was always the MAC address of the primary network interface (en0) without periods and all lower case. However, at some point along the way Apple changed their mind and now use a longer UUID formated string. But, to preserve compatibility, older hardware still uses the older value and newer hardware uses the new format. So you have to figure out what case you are in.
The value is stored in the IORegistry, and this is relatively easy to get ahold of either by scripting or in C. For scripting it is probably easiest to shell out and use the 'ioreg' command. Then you need to decide if you have an old-style machine, or one that uses the new style. This turns out to be easy because Apple prepends "00000000-0000-1000-8000-" to the MAC address for older computers. I have modified the script snippit that you can find on AFP548 a little and present this version (in bash form):
PLATFORM_UUID=`/usr/sbin/ioreg -rd1 -c IOPlatformExpertDevice | /usr/bin/awk '/IOPlatformUUID/ && gsub("\"", "") { print $3 }'`
if [[ "$PLATFORM_UUID" == 00000000-0000-1000-8000-* ]]; then
 PLATFORM_UUID=`echo "$PLATFORM_UUID" | /usr/bin/awk 'BEGIN { FS = "-" }; { print tolower($5) }'`
fi
I do have some concern about the output of ioreg could change at some point, breaking this script, but that is something you always have to worry about when using shell commands in  scripting. The Foundation/Cocoa way of doing this does not suffer from this, but you do have to dive into the IOKit to do so (there is no Obj-C way of doing this, so you have to use this snippit of C code):
NSString * byHostIDString = nil;
io_struct_inband_t iokit_entry;
uint32_t bufferSize = 4096; // this signals the longest entry we will take
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
IORegistryEntryGetProperty(ioRegistryRoot, kIOPlatformUUIDKey, iokit_entry, &bufferSize);
byHostIDString = [NSString stringWithCString:iokit_entry encoding:NSASCIIStringEncoding];
// older comptuers use only the mac address portion of the UUID string
if ([byHostIDString hasPrefix:@"00000000-0000-1000-8000-"]) {
 NSArray * UUIDElements = [byHostIDString componentsSeparatedByString:@"-"];
 byHostIDString = [(NSString *)[UUIDElements objectAtIndex:[UUIDElements count] -1] lowercaseString];
}
IOObjectRelease((unsigned int) iokit_entry);
IOObjectRelease(ioRegistryRoot);
You will need to include Foundation and IOKit in your project, and if you are actually using this code you might want to put a few more checks in there to make sure that you don't get NULL or nil back from anything. I have never seem those code paths active on my implementations.
I don't know who first figured out the scripting method of figuring out the ByHost key, but I have been using AFP548's article on the subject as my reference on this one for a while. There are some important notes in the comments, so read more than just the article.

Tuesday, April 14, 2009

Improving on NSLog

While developing I am a big user of logging. Having a well-formated series of reports from my half-developed program telling me that everything has worked up to that point is very comforting to me. It is also really nice when something goes horribly wrong to be able to dial-up the logging on a program or script to help figure out what is going on. But when running a program day-to-day I don't want all the noise clogging up my system logs.
Up until very recently I have been using mixtures of NSLog, printf/fprintf, and syslog to get my debugging logged, but each one of those has their limitations: NSLog does not have any sense of "log level", the printf/fprintf combination gives me two levels of logging, but I then have to manage all of that and figure out where to store things, and syslog mostly matches my thinking but does not integrate completely into a Cocoa program.
Then I read Peter Hosey's series of posts about ASL (Apple System Logger). ASL does have glaring hole for Cocoa programming: it does not understand NSStrings. But that same series of posts had the solution to that: use a #define statement to convert the format and arguments to a single UTF string, which ASL is very happy to take. He even provided a line to do so: 
#define asl_NSLog(client, msg, level, format, ...) asl_log(client, msg, level, "%s", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String])
This was almost enough, but there are still a few things I was not happy with: I only use the default client and message, and there are times I want to have the debugging output to go to stdout/stderr in addition to the system logs. So I have modified Peter's example and come up with the following:
// Inspired by Peter Hosey's series on asl_log
#ifndef ASL_KEY_FACILITY
 #define ASL_KEY_FACILITY "Facility"
#endif

#define LOG_LEVEL_TO_PRINT ASL_LEVEL_WARNING
#define NSLog_level(log_level, format, ...) asl_log(NULL, NULL, log_level, "%s", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); if (log_level >= LOG_LEVEL_TO_PRINT) { if (log_level "%s\n", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); } else { printf("%s\n", [[NSString stringWithFormat:format, ##__VA_ARGS__] UTF8String]); } }

#define NSLog_emerg(format, ...) NSLog_level(ASL_LEVEL_EMERG, format, ##__VA_ARGS__)
#define NSLog_alert(format, ...) NSLog_level(ASL_LEVEL_ALERT, format, ##__VA_ARGS__)
#define NSLog_crit(format, ...) NSLog_level(ASL_LEVEL_CRIT, format, ##__VA_ARGS__)
#define NSLog_error(format, ...) NSLog_level(ASL_LEVEL_ERR, format, ##__VA_ARGS__)
#define NSLog_warn(format, ...) NSLog_level(ASL_LEVEL_WARNING, format, ##__VA_ARGS__)
#define NSLog_notice(format, ...) NSLog_level(ASL_LEVEL_NOTICE, format, ##__VA_ARGS__)
#define NSLog_info(format, ...) NSLog_level(ASL_LEVEL_INFO, format, ##__VA_ARGS__)
#define NSLog_debug(format, ...) NSLog_level(ASL_LEVEL_DEBUG, format, ##__VA_ARGS__)
Now I have 8 functions that I can use as drop-in replacements for NSLog that will send the output to the system logging facility, and depending on the level I set with LOG_LEVEL_TO_PRINT, also to stdout/stderr. For me this also has the nice benefit of getting rid of all of the timestamps in the XCode console when I am developing which helps me concentrate on my program's output better (while still preserving this information in the system log).

About this Blog

As an Mac administrator I have benefited greatly from the writings of other Mac administrators such as Greg Neagle and those who have contributed so much to MacEnterprise and AFP548. So I would like to contribute some of my own content.
I have already done so in a few cases, and will try to continue to do so on mailing lists and forums, but I would like to have something at the end of the day that I can point to and call mine. This blog hopefully will also allow me to publish smaller hints that would not be appropriate for full posts on the larger sites. The other main benefit of doing this on a site I can control is that I can go back and revise things as they change.
My job leads me to do a lot of things that straddle the boarder between scripting and programming, so the posts will probably reflect that mixture.