Next: , Previous: Preparation, Up: Top


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 illustrate this.

controlflow.png

The third step may look the most complex, but for a simple client it will actually not involve any code. If your application need to handle several concurrent clients, or if it is a server that need 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 username or passwords, 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 our 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, that have several clients running simultaneous, instead of simply calling 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. The steps that make up it are: 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 continue until authentication succeeds or there is an error. The format of the data to transfer, 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 do not send data to the server itself, but return it in an buffer. You must send it to the server yourself, according to an application protocol profile. For example, the SASL application protocol profile for SMTP is described in RFC 2554.

The following image illustrate the steps we have been talking about.

controlflow2.png

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.  */
       gsasl_property_set (session, GSASL_AUTHID, "jas");
       gsasl_property_set (session, GSASL_PASSWORD, "secret");
     
       /* Do it. */
       client_authenticate (ctx, 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 create a new session, store the username and password in the session handle, then call another function client_authenticate to handle the authentication loop, and end by cleaning up. Let's continue with the implementation of client_authenticate.

     void client_authenticate (Gsasl * ctx, Gsasl_session * session)
     {
       char buf[BUFSIZ] = "";
       char *p;
       int rc;
     
       /* This loop mimic a protocol where the server get to send 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 need 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 send data first. For some mechanisms, this may waste a roundtrip, because the server need 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 depend 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 gsasl_step64 (and of course also gasl_step) return two non-error return codes. GSASL_OK is used for success, indicating that the library consider 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 reply “OK” but these function has 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 conclude 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 mechanism 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) include 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, but rather let 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 mechanism that server support, separate by SPC:\n");
       fgets (mechlist, sizeof (mechlist) - 1, stdin);
     
       suggestion = gsasl_client_suggest_mechanism (ctx, mechlist);
       if (suggestion)
         printf ("Library suggest 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 of mechanism that server support, separate by SPC:
     CRAM-MD5 DIGEST-MD5 GSSAPI FOO BAR
     Library suggest 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 only ever need an username and a password. But some mechanism require 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 abort the authentication process? Then your application have 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 need 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 abort, 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) specify 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';
     
           gsasl_property_set (sctx, GSASL_PASSCODE, buf);
           rc = GSASL_OK;
           break;
     
         case GSASL_AUTHID:
           printf ("Enter username:\n");
           fgets (buf, sizeof (buf) - 1, stdin);
           buf[strlen (buf) - 1] = '\0';
     
           gsasl_property_set (sctx, GSASL_AUTHID, buf);
           rc = GSASL_OK;
           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 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.