5. Authenticator Modules

What’s An Authenticator Module?

Authenticator modules provide credentials for outgoing connections from AMPS. It is not necessary for these credentials to match the account that AMPS runs under: the authenticator module has complete freedom to pass whatever credentials are appropriate. Most often, options provided in the configuration for the module specify how to obtain the credentials for this connection.

When to Implement an Authenticator

Implement an authenticator when you need to provide credentials to replication destinations and need to store those credentials outside of the AMPS configuration file. The AMPS default authenticator provides the ability to provide a user name and password.

Authenticator Context

AMPS uses authenticator modules when making an outgoing connection that uses authentication. AMPS creates a separate context for each outgoing connection. The same module may have different configuration settings, and provide different credentials, for different outgoing connections. For example, replication to a server at the same site may use different credentials than replication to a server at a different site.

Because the same module may be used with different parameters on different transports, AMPS allows you to save the parameters in an authenticator context.

Your module implements the amps_authenticator_create_context function to create an authentication context. The function has this signature:

amps_authenticator_context amps_authenticator_create_context(amps_module_options options);

The authenticator context is a pointer to data that your module defines. The AMPS server does not process or interpret this data. The server receives the pointer from your module when the module is initialized, and then provides the pointer back to your module for each authenticator request.

If your module is unable to successfully create the context, you return a NULL context and use the amps_logger provided to your module to log information about the problem.

Notice that you cast the pointer to amps_authenticator_context before you return it, and cast the pointer back to the type that your application uses after AMPS returns the pointer to you.

AMPS passes configuration options to modules as a pointer to an array of amps_option structs. AMPS indicates the end of the array with an option that has a NULL key. Your module processes options until it reaches an option with a NULL key. If no options are specified in the configuration file, the first amps_option in the array will have a NULL key.

The following snippet shows one way to process options:

for (; options->key != NULL; ++options) {
    // process the key/value pair here
}

When AMPS exits, AMPS calls amps_authenticator_destroy_context with each of the authenticator contexts created. This gives your module an opportunity to do any resource management, cleanup or logging necessary before AMPS shuts down. The function has this signature:

int amps_authenticator_destroy_context(amps_authenticator_context context);

Providing User Credentials for Logon

Authenticator modules implement three functions for providing user credentials.

Function  
amps_authenticator_logon_begin Called when AMPS begins the logon process. This function returns the username and password for AMPS to use to log in to the remote instance.
amps_authenticator_logon_retry Called when the remote side issues a challenge that requires a response. AMPS provides the challenge and the module returns the response to the challenge.
amps_authenticator_logon_complete Called when the logon sequence is complete. AMPS provides the final response from the server and a code indicating the reason that the login sequence is complete.

Table 5.1: Functions for Providing User Credentials

To implement an authenticator, your module implements each of these methods.

When AMPS prepares to create an outgoing connection using your module, AMPS calls the following method:

int amps_authenticator_logon_begin(amps_authenticator_context context,
                                   char**        userOut,
                                   size_t*       userOutLength,
                                   char**        passwdOut,
                                   size_t*       passwdOutLength)

The authenticator context provided is the pointer returned by the call to amps_authenticator_create_context() for the transport that received the request. Your module returns two character strings, one for the user name to provide to the remote instance and one for the password to provide to the remote instance. AMPS takes ownership of the memory associated with these strings, and therefore these strings must be allocated with the amps_allocator provided during module initialization.

Following is a simple, minimal implementation of amps_authenticator_logon_begin:

int amps_authenticator_logon_begin(amps_authenticator_context context,
                                   char**        userOut,
                                   size_t*       userOutLength,
                                   char**        passwdOut,
                                   size_t*       passwdOutLength)
{
    // for demonstration purposes, use a constant string
    const char *userString = "static_username";
    const char *passString = "static_password";

    size_t userLen = strlen(userString);
    size_t passLen = strlen(passString);

    // allocate memory for the user name using the allocator provided
    // during module initialization
    *userOut = amps_allocate(userLen);

    // copy the user name to the allocated memory
    strncpy(*userOut, userString, userLen);

    // allocate memory for the password and copy the password to the
    // allocated memory.
    *passwdOut = amps_allocate(passLen);
    strncpy(*passwdOut, userPass, passLen);

    // return success
    return AMPS_SUCCESS;
}

This simple implementation uses a static string for username and password, and is intended only to show correct usage of the interface.

The remote server can return a challenge by issuing a retry response to the login. For this method, the server passes the context and fills in the user, userLength, passwd, and passwdLength fields. The module is responsible for responding to the challenge by setting the userOut, userOutLength, passwdOut, and passwdOutLength parameters to respond to the challenge. The values in the out parameters are returned to the remote server as the response to the challenge.

int amps_authenticator_logon_retry(amps_authenticator_context context,
                                   const char*   user,
                                   size_t        userLength,
                                   const char*   passwd,
                                   size_t        passwdLength,
                                   char**        userOut,
                                   size_t*       userOutLength,
                                   char**        passwdOut,
                                   size_t*       passwdOutLength);

For sample purposes, we assume a pre-existing function that returns the appropriate response to the challenge, and implement amps_authenticator_logon_retry as follows:

int amps_authenticator_logon_retry(amps_authenticator_context context,
                                   const char*   user,
                                   size_t        userLength,
                                   const char*   passwd,
                                   size_t        passwdLength,
                                   char**        userOut,
                                   size_t*       userOutLength,
                                   char**        passwdOut,
                                   size_t*       passwdOutLength)
{
    char* challengeResponse = NULL;
    if (userLength == 0 || passwdLength == 0) { return AMPS_FAILURE; }

    // generate_response is assumed to take the provided challenge
    // and generate a response which is a NULL-terminated string
    challengeResponse = generate_response(user, userLength, passwd, passwdLength);

    if (challengeResponse == NULL) { return AMPS_FAILURE; }

    *passwdOutLength = strlen(challengeResponse);

    // allocate AMPS-managed memory for the response to the challenge
    *passwdOut = amps_allocate(*passwdOutLength);
    if (*passwdOut == NULL) {
        *passwdOutLength = 0;
        free(challengeResponse);
        return AMPS_FAILURE;
    }
    memcpy(*passwdOut, challengeResponse, *passwdOutLength);
    free(challengeResponse);

    *userOut = amps_allocate(userLength);
    if (*userOut == NULL) { return AMPS_FAILURE; }

    *userOutLength = userLength;

    memcpy(*userOut, user, userLength);

    return AMPS_SUCCESS;

}

The authenticator module interface also provides a function that AMPS calls when the logon process is complete. This gives the module the opportunity to do any cleanup that may be associated with the login process, and to log the reason code from the server if necessary. This function has the following signature:

int amps_authenticator_logon_completed(amps_authenticator_context context,
                                       char*         user,
                                       size_t        userLength,
                                       char*         passwd,
                                       size_t        passwdLength,
                                       char*         reason,
                                       size_t        reasonLength);

For example, the following implementation of this function logs the results of the login process, using the amps_logger stored during module initialization:

int amps_authenticator_logon_completed(amps_authenticator_context context,
                                       char*         user,
                                       size_t        userLength,
                                       char*         passwd,
                                       size_t        passwdLength,
                                       char*         reason,
                                       size_t        reasonLength)
{
  char buffer[1024];
  snprintf(buffer, sizeof(buffer),
     "AMPS user '%.*s' completed replication logon for reason '%.*s'",
     (int)userLength, user, (int)reasonLength, reason);
  LOGGER(amps_module_log_level_trace, buffer);

  return AMPS_SUCCESS;
}