Thursday, September 20, 2007

Policy, Mechanism and Time zones

In my last post I looked at D-Bus system bus activation and provided a step-by-step guide on how to use this. In this installment we’re going to look at a specific application of system bus activation; namely how it can be used from a desktop application to do a privileged operation: setting the timezone. This example will also feature usage of PolicyKit for lock down.

When dealing with programs that needs to do privileged operations, the first thing that one needs to do is to separate the mechanism (the piece of code actually doing the privileged bits) into a separate process from the rest of the program. The main program will then simply launch a small privileged helper whenever something privileged needs to happen. This is a good idea because it’s a lot easier to review a tiny privileged helper with minimal dependencies rather than a huge program that pulls in things like GTK+, Python, image loaders and what not. Typically, the privileged helper is written in a way such that it trusts no-one; it verifies all the incoming parameters and also checks whether the program that calls it should be allowed to do so. The latter part is where PolicyKit comes in - PolicyKit is an application-level toolkit that allows a mechanism to ask “is $USER allowed to do $ACTION?”. As such, PolicyKit comes with a set of data structures to help model these constructs.

Anyway, enough boring theory; the user experience we want is that the user can click on his predefined list of time zones to set it; something like this

modified intlclock

If the user is not privileged to do this action, we may show a dialog like this

stock PolicyKit-gnome dialog

that may (or may not) feature buttons to make the system remember that setting the time zone is OK. Such that the system won’t ask for authentication in the future. In other words, this is a one-time-pain dialog.

Let’s go through the code and see how all this works. First, we need to write a mechanism. This will need to run as uid 0 (e.g. root) and we choose D-Bus as the interface for using the mechanism. We choose a unique name for the service, org.gnome.ClockApplet.Mechanism and since we’d like to write it in C using dbus-glib, we need to write some XML to define the objects and interfaces that this service will expose. This is defined in this file. As evident, this service exposes a single object, /, that exposes a single interface org.gnome.ClockApplet.Mechanism that currently exposes a single method SetTimezone(string zonefile) where the argument to that function is a path to what time zone we want to use. The mechanism itself consists of two C files, available here and here and a single C header file, available here.

Then, exactly as in the last post we need a D-Bus configuration file and a D-Bus service file. There’s also a cheesy Makefile to tie it all together.

Now, with this mechanism in place, we can poke it via D-Bus

$ dbus-send --system --print-reply --dest=org.gnome.ClockApplet.Mechanism / org.gnome.ClockApplet.Mechanism.SetTimezone string:/usr/share/zoneinfo/Europe/Copenhagen
method return sender=:1.346 -> dest=:1.345 reply_serial=2

The usage of PolicyKit deserves some explanation. First of all, as stated in the docs, we need to declare one or more actions for our mechanism. As we only export a single action right now (setting the time zone), we declare only that one action via the PolicyKit .policy file here. When we extend the mechanism to e.g. provide functionality to set the time as well (via e.g. a SetTime(int64 secs_since_epoch) method) we can declare an action org.gnome.clockapplet.mechanism.settime in addition to our org.gnome.clockapplet.mechanism.settimezone one.

Now, whenever someone calls into the mechanism, we simply use libpolkit to query whether the caller is allowed to do the specific action:

sender = dbus_g_method_get_sender (context);
pk_caller = polkit_caller_new_from_dbus_name (system_bus_connection, sender, &dbus_error);
pk_action = polkit_action_new ();
polkit_action_set_action_id (pk_action, "org.gnome.clockapplet.mechanism.settimezone");
pk_result = polkit_context_can_caller_do_action (pk_context, pk_action, pk_caller);
polkit_caller_unref (pk_caller);
polkit_action_unref (pk_action);
if (pk_result != POLKIT_RESULT_YES) { /* bail out */ }

The system administrator can control, using the /etc/PolicyKit/PolicyKit.conf configuration file, exactly what users are allowed to do this and whether they are allowed to keep the privilege to do the action on a forever- or per-session basis. This shouldn’t be necessary since it’s possible to specify the default result for a given action. Distributors can tweak the .policy as they see fit; for example consumer-oriented distributions might want to patch this to make the defaults always be “yes” (to avoid the auth dialog entirely) and workstation-oriented distributions might want the defaults to be “auth_admin” (to require the system administrator password and not give a chance to remember the privilege). Finally, sites can override this using the system-wide configuration file, e.g. if I’m a huge site I can distribute a PolicyKit.conf file that allows certain users to always do an action, certain other users never to do an action, and finally a third set of users for whom I want them to enter their own (or the root) password. Again, see the /etc/PolicyKit/PolicyKit.conf configuration file for details.

The screenshots above show integration with the intlclock applet; that’s thanks to Matthias Clasen. I simply handed Matthias the mechanism code including a simple GTK demo application available here. This application simply attempts to call into the mechanism and if the mechanism throws the org.gnome.ClockApplet.Mechanism.NotPrivileged exception it uses PolicyKit-gnome (via the session bus) to prompt the user for authentication if applicable.

The PolicyKit stuff is mostly done; at least the plumbing is there. What I like to see is some more GNOME-ish porcelain to make this stuff even easier to use. First, it’s simply way too complicated to write dbus-glib D-Bus servers. There’s just way too much code; compare to the Python server in the last post. Second, I’d like to provide a small library with a subclassed GtkButton, e.g. PolicyKitGtkButton such that all the magic happens there and it shows e.g. a lock only if authentication will be needed. I’m also planning to do a song and dance at the Boston Summit about this. The list of users of PolicyKit right now includes only HAL. However, PackageKit, dconf and virt-manager are all in the process of picking it up. I’m also hoping NetworkManager and things like gnome-system-monitor will pick it up (for the process killing bits instead of falling back to running the entire UI as uid 0 just for this). With time, and not too much time, I’m hoping for PolicyKit to be a blessed dependency in GNOME and banish the idea of su/sudo wrappers to enable running X11 applications as uid 0.