Next: , Previous: , Up: GNU Simple Authentication and Security Layer   [Contents][Index]


3 Using the Library

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.

gsasl-controlflow

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.

gsasl-controlflow2

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.

3.1 Choosing a mechanism

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.

3.2 Using a callback

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]