Using SASL: CMU's Cyrus SASL Library
Pages: 1, 2

4. Client-side Interactions

Earlier, you might recall that a callback was defined as having a procedure (that your program supplies) that gets invoked when Cyrus SASL needs some information. The only weakness in this approach is if you need to ask the user several related questions for the same transaction -- e.g., "what is your username and password?" To get around this, Cyrus SASL lets you specify a special "interaction" callback, which gets invoked with a bunch of questions at once. When the callback procedure is invoked, it can construct whatever kind of dialog is appropriate to get whatever information is needed, without having to keep going back to the user.

So, what's the callback procedure do? Well, that's entirely up to you -- maybe it gets the necessary information from the command line, a configuration file, or a database; or maybe it just prompts the user. In practice, the answer is often different depending on what Cyrus SASL needs to know. (Take a look at the file doc/options.html for a list of the configuration options that Cyrus SASL and its plug-ins look for.)

OK, so Cyrus SASL has decided which plug-ins meet everyone's requirements, and has found the one that has all the information it needs to run. It's smooth sailing from here on out:

  1. Your program, the client, tells the server what Cyrus SASL said.
  2. The server says either "success," "continue," or "error," and may provide some additional data.
  3. If you get back "error," your program figures out what to do (start over, drop the connection, dump core, whatever).
  4. If you get back either "continue" or possibly "success," then you give whatever data came from the server to Cyrus SASL, which returns an indication of whether any more round-trips are needed, possibly along with some new data to give to the server.
  5. Now you compare what both the server and Cyrus SASL think about whether any more round-trips are needed. If they disagree, then there's a protocol misinterpretation somewhere, so your program should abort the connection.
  6. If both sides agree there won't be any more round-trips, the client is authenticated, and it's time to do some real work.
  7. Otherwise, go back to Step 1.

The only "tricky" part in this loop is at Step 4 where you may have to call Cyrus SASL if the server returns "success." The reason is that some protocols are able to pass back data and indicate success at the same time. (A lot of times this is used for mutual authentication -- the server authenticates itself to the client.) If this is the case with your protocol, you have to tell Cyrus SASL and invoke Step 4 on success.

Let's continue with using SMTP as an example. Your program tells Cyrus SASL that it's ready to start (we've already dealt with the TLS issue and created a client context). Cyrus SASL tells your program:

  • it wants to use the OTP mechanism; and
  • it's got some initial data to pass to the server.

The data in a SASL exchange is binary, so SMTP says you always have to base64-encode it.

So this is what your program, the client, sends to the server and what the server sends back in return (Steps 1 and 2, above):

S: 334 b3RwLXNoYTEgOTk5NyBwaXh5bWlzYXM4NTgwNSBleHQ=

Since your program didn't get back "error," Step 3 doesn't apply; but Step 4 does apply, so your program undoes the base64-encoding and gives the result to Cyrus SASL, which gives back some new data and indicates that another round-trip is needed. Since the server is waiting for more data (334), everything's in sync (Step 5), and Step 6 doesn't apply. So, Step 7 says to go back to Step 1.

This time, the exchange looks like this:

C: d29yZDpmZXJuIGhhbmcgYnJvdyBib25nIGhlcmQgdG9n
S: 235

The 235 response from the server indicates "success," so Step 3 doesn't apply. The server didn't supply any more data for Step 4 (but you still call Cyrus SASL because SMTP can pass back data on success), which doesn't return any data, and indicates that you should be done (Step 5). This is what the server said (Step 6), so your program has successfully authenticated itself and we're done.

5. Authentication Datastore

Before using Cyrus SASL in your server, you need to think about how each plug-in is going to validate the authentication information provided by a remote user. (Note that we didn't use terms like "password" here -- the plug-in may not be given a password, it may get a response to a challenge or some other bit of security trickery!) The answer, of course, is that Cyrus SASL will use a callback to figure this out. However, instead of requiring that the network administrator write a customized callback procedure, Cyrus SASL provides a couple of easy options.

To begin, you probably have some kind of a password system already running in your enterprise. For example, if you're already running Kerberos version 4 or 5, then the right thing just happens -- providing, of course, that the client requests the use of the KERBEROS_V4 or GSSAPI mechanism.

Otherwise, you probably have something that takes a username and a password and comes back with either a "yes" or "no." Cyrus SASL comes with a program called saslauthd that knows about several different mechanisms, such as password files, shadow password files, DCE, PAM, SIA, and so on. (The reason Cyrus SASL provides this as a separate program is so that your server doesn't have to run as root; instead, it talks to saslauthd using a named pipe.) Perhaps the most amusing mechanism is "remote IMAP," where saslauthd will take the username and password it's given and use that information to try to login to an IMAP server.

Finally, Cyrus SASL has its own authentication database. There are a couple of reasons why you might want to go down this path:

  • you want to support a "shared secret" mechanism (e.g., CRAM-MD5 or DIGEST-MD5), which requires either the actual passphrase or a mechanism-specific hashed-format; or
  • you want to use a mechanism that requires arbitrary authentication information (e.g., seed and sequence information for the OTP mechanism); or
  • you want to have a namespace for SASL users that's separate from what's provided by your operating system -- you don't have to create a UNIX login for each user of the service.

Cyrus SASL provides another program, saslpasswd, that lets the administrator create, modify, or remove SASL-login accounts.

So, how do you tell Cyrus SASL which option to select? The easiest way is to create a Cyrus SASL configuration file for your Server -- when your program initializes the library, one of the arguments it provides is used to determine the name of the appropriate configuration file. The other way is to define a callback to get configuration information.

6. Server-side SASL

Now that we've gotten the configuration story out of the way, let's look at how it comes together. Server-side code tends to be more event-driven than procedural, but the duality with the client-side is obvious.

The first step is to call the routine that initializes Cyrus SASL and pass it your global callback list. Then, whenever you get an incoming connection, your program creates a server context for it. Your program also remembers that it's in the "unauthenticated" state.

Next, whenever your program, the server, is in the unauthenticated state and the client asks for a list of available mechanisms, you tell Cyrus SASL the actual and desired security level for this connection, and ask it what mechanisms are available. Then, your program, the server, tells the client what Cyrus SASL said:

  • If Cyrus SASL said "error," you remain in the unauthenticated state.
  • If Cyrus SASL said "success," you enter the authenticated state (e.g., for the ANONYMOUS mechanism).
  • If Cyrus SASL said "continue," you enter the "authentication in-progress" state.

Similarly, whenever your program is in the "authentication in-progress" state, and the client sends back more SASL information, your program gives the information to Cyrus SASL. Then, your program tells the client what Cyrus SASL state:

  • If Cyrus SASL said "error," you go back to the unauthenticated state.
  • If Cyrus SASL said "success," you enter the authenticated state.
  • If Cyrus SASL said "continue," you remain in the "authentication in-progress" state.

Once you're in the authenticated state, you can ask Cyrus SASL to tell you what username the client is authorized as -- this lets your program apply whatever authorization policy is appropriate. For example, a client that authenticates as "mrose" probably has a different set of permissions than one that authenticates as "postmaster". SASL's job is to handle the authentication; the authorization part is up to you.

This leads us to one of the more clever things SASL does. It distinguishes between:

  • the authentication identity, which is the username you login as; and
  • the authorization identity, which is the username you want to act on behalf of.

In Cyrus SASL, these are referred to as the authid and authzid, respectively.

Thus far, everything we've talked about deals with authentication, not authorization. However, when a client authenticates, it optionally provides an authorization as well. In that case, in addition to all the other authentication-specific activities, Cyrus SASL will invoke a server-only callback asking if the authid is allowed to act on behalf of the authzid. Your program uses its configuration information to decide the answer. For example, the mail server will probably allow "postmaster" to do things on behalf of "mrose"; however, the backup server probably will not.

The nice part, of course, is that once you're in the authenticated state and you ask Cyrus SASL to tell you what username the client is authorized as, it returns the authzid (if provided). So your authorization code works transparently.

7. Finally

Regardless of whether you're doing client-side or server-side SASL, after authentication is complete, you should check the actual security level for the connection. If Cyrus SASL negotiated the use of either integrity-checking or encryption, then instead of reading/writing data directly to the network, you call a routine that applies the appropriate security transformations.

For example, perhaps the client authenticated with DIGEST-MD5 and integrity-checking was enabled. In that case, whenever the client or server receives data from the network, it calls a Cyrus SASL routine that verifies the checksum, and returns the actual data (or returns an error if there's evidence of tampering). Similarly, before doing any writes to the network, both the client and server call a Cyrus SASL routine to put a security wrapper around the data.

Cyrus SASL is a well-architected piece of software that offers a lot of integration flexibility. In this article, we've really just talked about the "vanilla" usage scenarios. There are several interesting features that Cyrus SASL supports that we've skipped over.

For example:

  • as a programmer, you may want to implement your own SASL mechanism -- perhaps something that interfaces to your enterprise's authentication system; or
  • you may want to implement a different database backend; similarly,
  • as an administrator, you want to be able to support different "realms" within your enterprise.

Fortunately, Cyrus SASL comes with a lot of documentation for both administrators and programmers -- start your browser at doc/index.html.

Marshall T. Rose is the prime mover of the BEEP Protocol. In his former position as the Internet Engineering Task Force (IETF) area director for network management, he was one of a dozen individuals who oversaw the Internet's standardization process.