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
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 malloc
ing and free
ing 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.
No comments:
Post a Comment