24.2.4.9 Writing Authentication Plugins

24.2.4.9.1 Writing the Server-Side Authentication Plugin
24.2.4.9.2 Writing the Client-Side Authentication Plugin
24.2.4.9.3 Using the Authentication Plugins
24.2.4.9.4 Implementing Proxy User Support in Authentication Plugins

MySQL supports pluggable authentication, in which plugins are invoked to authenticate client connections. Authentication plugins enable the use of authentication methods other than the built-in method of passwords stored in the mysql.user table. For example, plugins can be written to access external authentication methods. Also, authentication plugins can support the proxy user capability, such that the connecting user is a proxy for another user and is treated, for purposes of access control, as having the privileges of a different user. For more information, see Section 6.3.6, “Pluggable Authentication”, and Section 6.3.8, “Proxy Users”.

An authentication plugin can be written for the server side or the client side. Server-side plugins use the same plugin API that is used for the other server plugin types such as full-text parser or audit plugins (although with a different type-specific descriptor). Client-side plugins use the client plugin API.

Several header files contain information relevant to authentication plugins:

To write an authentication plugin, include the following header files in the plugin source file. Other MySQL or general header files might also be needed, depending on the plugin capabilities and requirements.

plugin_auth.h includes plugin.h and plugin_auth_common.h, so you need not include the latter files explicitly.

This section describes how to write a pair of simple server and client authentication plugins that work together.

Warning

These plugins accept any non-empty password and the password is sent in clear text. This is insecure, so the plugins should not be used in production environments.

The server-side and client-side plugins developed here both are named auth_simple. As described in Section 24.2.4.2, “Plugin Data Structures”, the plugin library file must have the same basename as the client plugin, so the source file name is auth_simple.c and produces a library named auth_simple.so (assuming that your system uses .so as the suffix for library files).

In MySQL source distributions, authentication plugin source is located in the plugin/auth directory and can be examined as a guide to writing other authentication plugins. Also, to see how the built-in authentication plugins are implemented, see sql/sql_acl.cc for plugins that are built in to the MySQL server and sql-common/client.c for plugins that are built in to the libmysqlclient client library. (For the built-in client plugins, note that the auth_plugin_t structures used there differ from the structures used with the usual client plugin declaration macros. In particular, the first two members are provided explicitly, not by declaration macros.)

24.2.4.9.1 Writing the Server-Side Authentication Plugin

Declare the server-side plugin with the usual general descriptor format that is used for all server plugin types (see Section 24.2.4.2.1, “Server Plugin Library and Plugin Descriptors”). For the auth_simple plugin, the descriptor looks like this:

mysql_declare_plugin(auth_simple)
{
  MYSQL_AUTHENTICATION_PLUGIN,
  &auth_simple_handler,                 /* type-specific descriptor */
  "auth_simple",                        /* plugin name */
  "Author Name",                        /* author */
  "Any-password authentication plugin", /* description */
  PLUGIN_LICENSE_GPL,                   /* license type */
  NULL,                                 /* no init function */
  NULL,                                 /* no deinit function */
  0x0100,                               /* version = 1.0 */
  NULL,                                 /* no status variables */
  NULL,                                 /* no system variables */
  NULL,                                 /* no reserved information */
  0                                     /* no flags */
}
mysql_declare_plugin_end;

The name member (auth_simple) indicates the name to use for references to the plugin in statements such as INSTALL PLUGIN or UNINSTALL PLUGIN. This is also the name displayed by SHOW PLUGINS or INFORMATION_SCHEMA.PLUGINS.

The auth_simple_handler member of the general descriptor points to the type-specific descriptor. For an authentication plugin, the type-specific descriptor is an instance of the st_mysql_auth structure (defined in plugin_auth.h):

struct st_mysql_auth
{
  int interface_version;
  const char *client_auth_plugin;
  int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info);
};

The st_mysql_auth structure has three members: The type-specific API version number, the client plugin name, and a pointer to the main plugin function that communicates with the client. The client_auth_plugin member should indicate the name of the client plugin if a specific plugin is required. A value of NULL means any plugin. In the latter case, whatever plugin the client uses will do. This is useful if the server plugin does not care about the client plugin or what user name or password it sends. For example, this might be true if the server plugin authenticates only local clients and uses some property of the operating system rather than the information sent by the client plugin.

For auth_simple, the type-specific descriptor looks like this:

static struct st_mysql_auth auth_simple_handler =
{
  MYSQL_AUTHENTICATION_INTERFACE_VERSION,
  "auth_simple",           /* required client-side plugin name */
  auth_simple_server       /* server-side plugin main function */
};

The main function takes two arguments representing an I/O structure and a MYSQL_SERVER_AUTH_INFO structure. The structure definition is found in plugin_auth.h. It looks like this:

typedef struct st_mysql_server_auth_info
{
  char *user_name;
  unsigned int user_name_length;
  const char *auth_string;
  unsigned long auth_string_length;
  char authenticated_as[MYSQL_USERNAME_LENGTH+1]; 
  char external_user[512];
  int  password_used;
  const char *host_or_ip;
  unsigned int host_or_ip_length;
} MYSQL_SERVER_AUTH_INFO;

The character set for string members is UTF-8. If there is a _length member associated with a string, it indicates the string length in bytes. Strings are also null-terminated.

When an authentication plugin is invoked by the server, it should interpret the MYSQL_SERVER_AUTH_INFO structure members as follows. Some of these are used to set the value of SQL functions or system variables within the client session, as indicated.

  • user_name: The user name sent by the client. The value becomes the USER() function value.

  • user_name_length: The length of user_name in bytes.

  • auth_string: The value of the authentication_string column of the mysql.user table row for the matching account name (that is, the row that matches the client user name and host name and that the server uses to determine how to authenticate the client).

    Suppose that you create an account using the following statement:

    CREATE USER 'my_user'@'localhost'
      IDENTIFIED WITH my_plugin AS 'my_auth_string';
    

    When my_user connects from the local host, the server invokes my_plugin and passes 'my_auth_string' to it as the auth_string value.

  • auth_string_length: The length of auth_string in bytes.

  • authenticated_as: The server sets this to the user name (the value of user_name). The plugin can alter it to indicate that the client should have the privileges of a different user. For example, if the plugin supports proxy users, the initial value is the name of the connecting (proxy) user, and the plugin can change this member to the proxied user name. The server then treats the proxy user as having the privileges of the proxied user (assuming that the other conditions for proxy user support are satisfied; see Section 24.2.4.9.4, “Implementing Proxy User Support in Authentication Plugins”). The value is represented as a string at most MYSQL_USER_NAME_LENGTH bytes long, plus a terminating null. The value becomes the CURRENT_USER() function value.

  • external_user: The server sets this to the empty string (null terminated). Its value becomes the external_user system variable value. If the plugin wants that system variable to have a different value, it should set this member accordingly; for example, to the connecting user name. The value is represented as a string at most 511 bytes long, plus a terminating null.

  • password_used: This member applies when authentication fails. The plugin can set it or ignore it. The value is used to construct the failure error message of Authentication fails. Password used: %s. The value of password_used determines how %s is handled, as shown in the following table.

    password_used%s Handling
    0NO
    1YES
    2There will be no %s
  • host_or_ip: The name of the client host if it can be resolved, or the IP address otherwise.

  • host_or_ip_length: The length of host_or_ip in bytes.

The auth_simple main function, auth_simple_server(), reads the password (a null-terminated string) from the client and succeeds if the password is nonempty (first byte not null):

static int auth_simple_server (MYSQL_PLUGIN_VIO *vio,
                               MYSQL_SERVER_AUTH_INFO *info)
{
  unsigned char *pkt;
  int pkt_len;

  /* read the password as null-terminated string, fail on error */
  if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
    return CR_ERROR;

  /* fail on empty password */
  if (!pkt_len || *pkt == '\0')
  {
    info->password_used= PASSWORD_USED_NO;
    return CR_ERROR;
  }

  /* accept any nonempty password */
  info->password_used= PASSWORD_USED_YES;

  return CR_OK;
}

The main function should return one of the error codes shown in the following table.

Error CodeMeaning
CR_OKSuccess
CR_OK_HANDSHAKE_COMPLETEDo not send a status packet back to client
CR_ERRORError

For an example of how the handshake works, see the plugin/auth/dialog.c source file.

auth_simple_server_main() is so basic that it does not use the authentication information structure except to set the member that indicates whether a password was received.

A plugin that supports proxy users must return to the server the name of the proxied user (the MySQL user whose privileges the client user should get). To do this, the plugin must set the info->authenticated_as member to the proxied user name. For information about proxying, see Section 6.3.8, “Proxy Users”, and Section 24.2.4.9.4, “Implementing Proxy User Support in Authentication Plugins”.

24.2.4.9.2 Writing the Client-Side Authentication Plugin

Declare the client-side plugin descriptor with the mysql_declare_client_plugin() and mysql_end_client_plugin macros (see Section 24.2.4.2.3, “Client Plugin Descriptors”). For the auth_simple plugin, the descriptor looks like this:

mysql_declare_client_plugin(AUTHENTICATION)
  "auth_simple",                        /* plugin name */
  "Author Name",                        /* author */
  "Any-password authentication plugin", /* description */
  {1,0,0},                              /* version = 1.0.0 */
  "GPL",                                /* license type */
  NULL,                                 /* for internal use */
  NULL,                                 /* no init function */
  NULL,                                 /* no deinit function */
  NULL,                                 /* no option-handling function */
  auth_simple_client                    /* main function */
mysql_end_client_plugin;

The descriptor members from the plugin name through the option-handling function are common to all client plugin types. (For descriptions, see Section 24.2.4.2.3, “Client Plugin Descriptors”.) Following the common members, the descriptor has an additional member specific to authentication plugins. This is the main function, which handles communication with the server. The function takes two arguments representing an I/O structure and a connection handler. For our simple any-password plugin, the main function does nothing but write to the server the password provided by the user:

static int auth_simple_client (MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{
  int res;

  /* send password as null-terminated string in clear text */
  res= vio->write_packet(vio, (const unsigned char *) mysql->passwd, 
                         strlen(mysql->passwd) + 1);

  return res ? CR_ERROR : CR_OK;
}

The main function should return one of the error codes shown in the following table.

Error CodeMeaning
CR_OKSuccess
CR_OK_HANDSHAKE_COMPLETESuccess, client done
CR_ERRORError

CR_OK_HANDSHAKE_COMPLETE indicates that the client has done its part successfully and has read the last packet. A client plugin may return CR_OK_HANDSHAKE_COMPLETE if the number of round trips in the authentication protocol is not known in advance and the plugin must read another packet to determine whether authentication is finished.

24.2.4.9.3 Using the Authentication Plugins

To compile and install a plugin library object file, see the instructions in Section 24.2.4.3, “Compiling and Installing Plugin Libraries”. To use the library file, it must be installed in the plugin directory (the directory named by the plugin_dir system variable).

Register the server-side plugin with the server. For example, to load the plugin at server startup, use a --plugin-load=auth_simple.so option (change the library suffix as necessary for your system).

Create a user for whom the server will use the auth_simple plugin for authentication:

mysql> CREATE USER 'x'@'localhost'
    -> IDENTIFIED WITH auth_simple;

Use a client program to connect to the server as user x. The server-side auth_simple plugin communicates with the client program that it should use the client-side auth_simple plugin, and the latter sends the password to the server. The server plugin should reject connections that send an empty password and accept connections that send a nonempty password. Invoke the client program each way to verify this:

shell> mysql --user=x --skip-password
ERROR 1045 (28000): Access denied for user 'x'@'localhost' (using password: NO)

shell> mysql --user=x --password=abc
mysql>

Because the server plugin accepts any nonempty password, it should be considered insecure. After testing the plugin to verify that it works, restart the server without the --plugin-load option so as not to indavertently leave the server running with an insecure authentication plugin loaded. Also, drop the user with DROP USER 'x'@'localhost'.

For additional information about loading and using authentication plugins, see Section 5.1.8.1, “Installing and Uninstalling Plugins”, and Section 6.3.6, “Pluggable Authentication”.

If you are writing a client program that supports the use of authentication plugins, normally such a program causes a plugin to be loaded by calling mysql_options() to set the MYSQL_DEFAULT_AUTH and MYSQL_PLUGIN_DIR options:

char *plugin_dir = "path_to_plugin_dir";
char *default_auth = "plugin_name";

/* ... process command-line options ... */

mysql_options(&mysql, MYSQL_PLUGIN_DIR, plugin_dir);
mysql_options(&mysql, MYSQL_DEFAULT_AUTH, default_auth);

Typically, the program will also accept --plugin-dir and --default-auth options that enable users to override the default values.

Should a client program require lower-level plugin management, the client library contains functions that take an st_mysql_client_plugin argument. See Section 23.8.14, “C API Client Plugin Functions”.

24.2.4.9.4 Implementing Proxy User Support in Authentication Plugins

One of the capabilities that pluggable authentication makes possible is proxy users (see Section 6.3.8, “Proxy Users”). For a server-side authentication plugin to participate in proxy user support, these conditions must be satisfied:

  • When a connecting client should be treated as a proxy user, the plugin must return a different name in the authenticated_as member of the MYSQL_SERVER_AUTH_INFO structure, to indicate the proxied user name. It may also optionally set the external_user member, to set the value of the external_user system variable.

  • Proxy user accounts must be set up to be authenticated by the plugin. Use the CREATE USER or GRANT statement to associate accounts with plugins.

  • Proxy user accounts must have the PROXY privilege for the proxied accounts. Use the GRANT statement to grant this privilege.

In other words, the only aspect of proxy user support required of the plugin is that it set authenticated_as to the proxied user name. The rest is optional (setting external_user) or done by the DBA using SQL statements.

How does an authentication plugin determine which proxied user to return when the proxy user connects? That depends on the plugin. Typically, the plugin maps clients to proxied users based on the authentication string passed to it by the server. This string comes from the AS part of the IDENTIFIED WITH clause of the CREATE USER statement that specifies use of the plugin for authentication.

The plugin developer determines the syntax rules for the authentication string and implements the plugin according to those rules. Suppose that a plugin takes a comma-separated list of pairs that map external users to MySQL users. For example:

CREATE USER ''@'%.example.com'
  IDENTIFIED WITH my_plugin AS 'extuser1=mysqlusera, extuser2=mysqluserb'
CREATE USER ''@'%.example.org'
  IDENTIFIED WITH my_plugin AS 'extuser1=mysqluserc, extuser2=mysqluserd'

When the server invokes a plugin to authenticate a client, it passes the appropriate authentication string to the plugin. The plugin is responsible to:

  1. Parse the string into its components to determine the mapping to use

  2. Compare the client user name to the mapping

  3. Return the proper MySQL user name

For example, if extuser2 connects from an example.com host, the server passes 'extuser1=mysqlusera, extuser2=mysqluserb' to the plugin, and the plugin should copy mysqluserb into authenticated_as, with a terminating null byte. If extuser2 connects from an example.org host, the server passes 'extuser1=mysqluserc, extuser2=mysqluserd', and the plugin should copy mysqluserd instead.

If there is no match in the mapping, the action depends on the plugin. If a match is required, the plugin likely will return an error. Or the plugin might simply return the client name; in this case, it should not change authenticated_as, and the server will not treat the client as a proxy.

The following example demonstrates how to handle proxy users using a plugin named auth_simple_proxy. Like the auth_simple plugin described earlier, auth_simple_proxy accepts any nonempty password as valid (and thus should not be used in production environments). In addition, it examines the auth_string authentication string member and uses these very simple rules for interpreting it:

  • If the string is empty, the plugin returns the user name as given and no proxying occurs. That is, the plugin leaves the value of authenticated_as unchanged.

  • If the string is nonempty, the plugin treats it as the name of the proxied user and copies it to authenticated_as so that proxying occurs.

For testing, set up one account that is not proxied according to the preceding rules, and one that is. This means that one account has no AS clause, and one includes an AS clause that names the proxied user:

CREATE USER 'plugin_user1'@'localhost'
  IDENTIFIED WITH auth_simple_proxy;
CREATE USER 'plugin_user2'@'localhost'
  IDENTIFIED WITH auth_simple_proxy AS 'proxied_user';

In addition, create an account for the proxied user and grant plugin_user2 the PROXY privilege for it:

CREATE USER 'proxied_user'@'localhost'
  IDENTIFIED BY 'proxied_user_pass';
GRANT PROXY
  ON 'proxied_user'@'localhost'
  TO 'plugin_user2'@'localhost';

Before the server invokes an authentication plugin, it sets authenticated_as to the client user name. To indicate that the user is a proxy, the plugin should set authenticated_as to the proxied user name. For auth_simple_proxy, this means that it must examine the auth_string value, and, if the value is nonempty, copy it to the authenticated_as member to return it as the name of the proxied user. In addition, when proxying occurs, the plugin sets the external_user member to the client user name; this becomes the value of the external_user system variable.

static int auth_simple_proxy_server (MYSQL_PLUGIN_VIO *vio,
                                     MYSQL_SERVER_AUTH_INFO *info)
{
  unsigned char *pkt;
  int pkt_len;

  /* read the password as null-terminated string, fail on error */
  if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
    return CR_ERROR;

  /* fail on empty password */
  if (!pkt_len || *pkt == '\0')
  {
    info->password_used= PASSWORD_USED_NO;
    return CR_ERROR;
  }

  /* accept any nonempty password */
  info->password_used= PASSWORD_USED_YES;

  /* if authentication string is nonempty, use as proxied user name */
  /* and use client name as external_user value */
  if (info->auth_string_length > 0)
  {
    strcpy (info->authenticated_as, info->auth_string);
    strcpy (info->external_user, info->user_name);
  }

  return CR_OK;
}

After a successful connection, the USER() function should indicate the connecting client user and host name, and CURRENT_USER() should indicate the account whose privileges apply during the session. The latter value should be the connecting user account if no proxying occurs or the proxied account if proxying does occur.

Compile and install the plugin, then test it. First, connect as plugin_user1:

shell> mysql --user=plugin_user1 --password=x

In this case, there should be no proxying:

mysql> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1. row ***************************
         USER(): plugin_user1@localhost
 CURRENT_USER(): plugin_user1@localhost
   @@proxy_user: NULL
@@external_user: NULL

Then connect as plugin_user2:

shell> mysql --user=plugin_user2 --password=x

In this case, plugin_user2 should be proxied to proxied_user:

mysql> SELECT USER(), CURRENT_USER(), @@proxy_user, @@external_user\G
*************************** 1. row ***************************
         USER(): plugin_user2@localhost
 CURRENT_USER(): proxied_user@localhost
   @@proxy_user: 'plugin_user2'@'localhost'
@@external_user: 'plugin_user2'@'localhost'