Next: Properties, Previous: Preparation, Up: GNU Simple Authentication and Security Layer [Contents][Index]
Your application’s use of the library can be roughly modeled into the following steps: initialize the library, optionally specify the callback, perform the authentication, and finally clean up. The following image illustrates this.
The third step may look complex, but for a simple client it will actually not involve any code. If your application needs to handle several concurrent clients, or if it is a server that needs to serve many clients simultaneous, things do get a bit more complicated.
For illustration, we will write a simple client. Writing a server
would be similar, the only difference is that, later on, instead of
supplying a username and password, you need to decide whether someone
should be allowed to log in or not. The code for what we have
discussed so far make up the main
function in our client
(see Example 1):
int main (int argc, char *argv[]) { Gsasl *ctx = NULL; int rc; if ((rc = gsasl_init (&ctx)) != GSASL_OK) { printf ("Cannot initialize libgsasl (%d): %s", rc, gsasl_strerror (rc)); return 1; } client (ctx); gsasl_done (ctx); return 0; }
Here, the call to the function client
correspond to the third
step in the image above.
For a more complicated application, having several clients running
simultaneous, instead of a simple call to client
, it may have
created new threads for each session, and call client
within
each thread. The library is thread safe.
An actual authentication session is more complicated than what we have seen so far. These are the steps: decide which mechanism to use, start the session, optionally specify the callback, optionally set any properties, perform the authentication loop, and clean up. Naturally, your application will start to talk its own protocol (e.g., SMTP or IMAP) after these steps have concluded.
The authentication loop is based on sending tokens (typically short messages encoded in base 64) back and forth between the client and server. It continues until authentication succeeds or an error occurs. The format of the data to be transferred, the number of iterations in the loop, and other details are specified by each mechanism. The goal of the library is to isolate your application from the details of all different mechanisms.
Note that the library does not send data to the server itself, but returns it in an buffer. You must send it to the server, following an application protocol profile. For example, the SASL application protocol profile for SMTP is described in RFC 2554.
The following image illustrates the steps we have been talking about.
We will now show the implementation of the client
function used
before.
void client (Gsasl *ctx) { Gsasl_session *session; const char *mech = "PLAIN"; int rc; /* Create new authentication session. */ if ((rc = gsasl_client_start (ctx, mech, &session)) != GSASL_OK) { printf ("Cannot initialize client (%d): %s\n", rc, gsasl_strerror (rc)); return; } /* Set username and password in session handle. This info will be lost when this session is deallocated below. */ rc = gsasl_property_set (session, GSASL_AUTHID, "jas"); if (rc != GSASL_OK) { printf ("Cannot set property (%d): %s\n", rc, gsasl_strerror (rc)); return; } rc = gsasl_property_set (session, GSASL_PASSWORD, "secret"); if (rc != GSASL_OK) { printf ("Cannot set property (%d): %s\n", rc, gsasl_strerror (rc)); return; } /* Do it. */ client_authenticate (session); /* Cleanup. */ gsasl_finish (session); }
This function is responsible for deciding which mechanism to use. In
this case, the ‘PLAIN’ mechanism is hard coded, but you will see
later how this can be made more flexible. The function creates a new
session, then it stores the username and password in the session
handle, then it calls another function client_authenticate
to
handle the authentication loop, and finally it cleans up up. Let’s
continue with the implementation of client_authenticate
.
void client_authenticate (Gsasl_session * session) { char buf[BUFSIZ] = ""; char *p; int rc; /* This loop mimics a protocol where the server sends data first. */ do { printf ("Input base64 encoded data from server:\n"); fgets (buf, sizeof (buf) - 1, stdin); if (buf[strlen (buf) - 1] == '\n') buf[strlen (buf) - 1] = '\0'; rc = gsasl_step64 (session, buf, &p); if (rc == GSASL_NEEDS_MORE || rc == GSASL_OK) { printf ("Output:\n%s\n", p); free (p); } } while (rc == GSASL_NEEDS_MORE); printf ("\n"); if (rc != GSASL_OK) { printf ("Authentication error (%d): %s\n", rc, gsasl_strerror (rc)); return; } /* The client is done. Here you would typically check if the server let the client in. If not, you could try again. */ printf ("If server accepted us, we're done.\n"); }
This last function needs to be discussed in some detail. First, you should be aware that there are two versions of this function, that differ in a subtle way. The version above (see Example 2) is used for application profiles where the server sends data first. For some mechanisms, this may waste a roundtrip, because the server needs input from the client to proceed. Therefor, today the recommended approach is to permit client to send data first (see Example 1). Which version you should use depends on which application protocol you are implementing.
Further, you should realize that it is bad programming style to use a
fixed size buffer. On GNU systems, you may use the getline
functions instead of fgets
. However, in practice, there are
few mechanisms that use very large tokens. In typical configurations,
the mechanism with the largest tokens (GSSAPI) can use at least 500
bytes. A fixed buffer size of 8192 bytes may thus be sufficient for
now. But don’t say I didn’t warn you, when a future mechanism doesn’t
work in your application, because of a fixed size buffer.
The function gsasl_step64
(and of course also gasl_step
)
returns two non-error return codes. GSASL_OK
is used for
success, indicating that the library considers the authentication
finished. That may include a successful server authentication,
depending on the mechanism. You must not let the client continue to
the application protocol part unless you receive GSASL_OK
from
these functions. In particular, don’t be fooled into believing
authentication were successful if the server replies “OK” but these
functions have failed with an error. The server may have been hacked,
and could be tricking you into sending confidential data, without
having successfully authenticated the server.
The non-error return code GSASL_NEEDS_MORE
is used to signal to
your application that you should send the output token to the peer,
and wait for a new token, and do another iteration. If the server
concludes the authentication process, with no data, you should call
gsasl_step64
(or gsasl_step
) specifying a zero-length
token.
If the functions (gsasl_step
and gsasl_step64
) return
any non-error code, the content of the output buffer is undefined.
Otherwise, it is the callers responsibility to deallocate the buffer,
by calling free
. Note that in some situations, where the
buffer is empty, NULL
is returned as the buffer value. You
should treat this as an empty buffer.
Our earlier code was hard coded to use a specific mechanism. This is
rarely a good idea. Instead, it is recommended to select the best
mechanism available from the list of mechanisms supported by the
server. Note that without TLS or similar, the list may have been
maliciously altered, by an attacker. This means that you should abort
if you cannot find any mechanism that exceeds your minimum security
level. There is a function gsasl_client_suggest_mechanism
(see Global Functions) that will try to pick the “best”
available mechanism from a list of mechanisms. Our simple interactive
example client (see Example 3) includes the following function to
decide which mechanism to use. Note that the code doesn’t blindly use
what is returned from gsasl_client_suggest_mechanism
, rather it
lets some logic (in this case the user, through an interactive query)
decide which mechanism is acceptable.
const char *client_mechanism (Gsasl *ctx) { static char mech[GSASL_MAX_MECHANISM_SIZE + 1] = ""; char mechlist[BUFSIZ] = ""; const char *suggestion; printf ("Enter list of server supported mechanisms, separate by SPC:\n"); fgets (mechlist, sizeof (mechlist) - 1, stdin); suggestion = gsasl_client_suggest_mechanism (ctx, mechlist); if (suggestion) printf ("Library suggests use of `%s'.\n", suggestion); printf ("Enter mechanism to use:\n"); fgets (mech, sizeof (mech) - 1, stdin); mech[strlen (mech) - 1] = '\0'; return mech; }
When running this example code, it might look like in the following output.
Enter list server supported mechanisms, separate by SPC: CRAM-MD5 DIGEST-MD5 GSSAPI FOO BAR Library suggests use of `GSSAPI'. Enter mechanism to use: CRAM-MD5 Input base64 encoded data from server: Zm5vcmQ= Output: amFzIDkyY2U1NWE5MTM2ZTY4NzEyMTUyZTFjYmFmNjVkZjgx If server accepted us, we're done.
Our earlier code specified the username and password before the authentication loop, as in:
gsasl_property_set (ctx, GSASL_AUTHID, "jas"); gsasl_property_set (ctx, GSASL_PASSWORD, "secret");
This may work for simple mechanisms, that need only a username and a password. But some mechanism requires more information, such as an authorization identity, a special PIN or passcode, a realm, a hostname, a service name, or an anonymous identifier. Querying the user for all that information, without knowing exactly which of it is really needed will result in a poor user interface. The user should not have to input private information, if it isn’t required.
The approach is a bad idea for another reason. What if the server aborts the authentication process? Then your application has already queried the user for a username and password. It would be better if you only asked the user for this information, annoying to input, when it is known to be needed.
A better approach to this problem is to use a callback. Then the mechanism may query your application whenever it needs some information, like the username and password. It will only do this at the precise step in the authentication when the information is actually needed. Further, if the user aborts, e.g., a password prompt, the mechanism is directly informed of this (because it invoked the callback), and could recover somehow.
Our final example (see Example 4) specifies a callback function,
inside main
as below.
/* Set the callback handler for the library. */ gsasl_callback_set (ctx, callback);
The function itself is implemented as follows.
int callback (Gsasl * ctx, Gsasl_session * sctx, Gsasl_property prop) { char buf[BUFSIZ] = ""; int rc = GSASL_NO_CALLBACK; /* Get user info from user. */ printf ("Callback invoked, for property %d.\n", prop); switch (prop) { case GSASL_PASSCODE: printf ("Enter passcode:\n"); fgets (buf, sizeof (buf) - 1, stdin); buf[strlen (buf) - 1] = '\0'; rc = gsasl_property_set (sctx, GSASL_PASSCODE, buf); break; case GSASL_AUTHID: printf ("Enter username:\n"); fgets (buf, sizeof (buf) - 1, stdin); buf[strlen (buf) - 1] = '\0'; rc = gsasl_property_set (sctx, GSASL_AUTHID, buf); break; default: printf ("Unknown property! Don't worry.\n"); break; } return rc; }
Again, it is bad style to use a fixed size buffer. Mmm’kay.
Which properties you should handle is up to you. If you don’t know how
to respond to a certain property, simply return
GSASL_NO_CALLBACK
. The basic properties to support are
authentication identity (GSASL_AUTHID
), authorization identity
(GSASL_AUTHZID
), and password (GSASL_PASSWORD
).
See Properties, for the list of all properties, and what your callback
should (ideally) do for them, and which properties each mechanism
require in order to work.
Next: Properties, Previous: Preparation, Up: GNU Simple Authentication and Security Layer [Contents][Index]