summaryrefslogtreecommitdiffstats
path: root/plugin/auth_pam
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/auth_pam')
-rw-r--r--plugin/auth_pam/CMakeLists.txt70
-rw-r--r--plugin/auth_pam/auth_pam.c241
-rw-r--r--plugin/auth_pam/auth_pam_base.c179
-rw-r--r--plugin/auth_pam/auth_pam_common.c56
-rw-r--r--plugin/auth_pam/auth_pam_tool.c120
-rw-r--r--plugin/auth_pam/auth_pam_tool.h81
-rw-r--r--plugin/auth_pam/auth_pam_v1.c86
-rw-r--r--plugin/auth_pam/config.h.cmake5
-rw-r--r--plugin/auth_pam/mapper/pam_user_map.c277
-rw-r--r--plugin/auth_pam/mapper/user_map.conf13
-rw-r--r--plugin/auth_pam/testing/CMakeLists.txt15
-rw-r--r--plugin/auth_pam/testing/mariadb_mtr.conf4
-rw-r--r--plugin/auth_pam/testing/pam_mariadb_mtr.c84
13 files changed, 1231 insertions, 0 deletions
diff --git a/plugin/auth_pam/CMakeLists.txt b/plugin/auth_pam/CMakeLists.txt
new file mode 100644
index 00000000..f2cce80f
--- /dev/null
+++ b/plugin/auth_pam/CMakeLists.txt
@@ -0,0 +1,70 @@
+IF(WIN32)
+ RETURN()
+ENDIF()
+
+INCLUDE (CheckIncludeFiles)
+INCLUDE (CheckFunctionExists)
+
+CHECK_INCLUDE_FILES (security/pam_ext.h HAVE_PAM_EXT_H)
+CHECK_INCLUDE_FILES (security/pam_appl.h HAVE_PAM_APPL_H)
+CHECK_FUNCTION_EXISTS (strndup HAVE_STRNDUP)
+CHECK_FUNCTION_EXISTS (getgrouplist HAVE_GETGROUPLIST)
+
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR})
+
+# Check whether getgrouplist uses gtid_t for second and third arguments.
+SET(CMAKE_REQUIRED_FLAGS -Werror)
+CHECK_C_SOURCE_COMPILES(
+"
+#include <grp.h>
+#include <unistd.h>
+int main() {
+ char *arg_1= 0;
+ gid_t arg_2=0, arg_3;
+ int arg_4;
+ (void)getgrouplist(arg_1,arg_2,&arg_3,&arg_4);
+ return 0;
+}
+"
+HAVE_POSIX_GETGROUPLIST
+)
+SET(CMAKE_REQUIRED_FLAGS)
+
+SET(CMAKE_REQUIRED_LIBRARIES pam)
+CHECK_FUNCTION_EXISTS(pam_syslog HAVE_PAM_SYSLOG)
+SET(CMAKE_REQUIRED_LIBRARIES)
+
+IF(HAVE_PAM_APPL_H AND HAVE_GETGROUPLIST)
+ FIND_LIBRARY(PAM_LIBRARY pam) # for srpm build-depends detection
+ ADD_DEFINITIONS(-D_GNU_SOURCE)
+ MYSQL_ADD_PLUGIN(auth_pam_v1 auth_pam_v1.c LINK_LIBRARIES pam MODULE_ONLY)
+ MYSQL_ADD_PLUGIN(auth_pam auth_pam.c LINK_LIBRARIES pam ${CMAKE_DL_LIBS} MODULE_ONLY)
+ IF (TARGET auth_pam)
+ MYSQL_ADD_EXECUTABLE(auth_pam_tool auth_pam_tool.c DESTINATION ${INSTALL_PLUGINDIR}/auth_pam_tool_dir COMPONENT Server)
+ TARGET_LINK_LIBRARIES(auth_pam_tool pam)
+ IF (CMAKE_VERSION VERSION_LESS 3.10.0)
+ # cmake bug #14362
+ SET(user mysql)
+ ELSE()
+ SET(user "%{mysqld_user}")
+ ENDIF()
+ SET(CPACK_RPM_server_USER_FILELIST ${CPACK_RPM_server_USER_FILELIST}
+ "%attr(700,${user},-) ${INSTALL_PLUGINDIRABS}/auth_pam_tool_dir"
+ "%attr(4755,root,-) ${INSTALL_PLUGINDIRABS}/auth_pam_tool_dir/auth_pam_tool")
+ SET(CPACK_RPM_server_USER_FILELIST ${CPACK_RPM_server_USER_FILELIST} PARENT_SCOPE)
+ ENDIF()
+ IF(TARGET auth_pam OR TARGET auth_pam_v1)
+ ADD_SUBDIRECTORY(testing)
+ ADD_LIBRARY(pam_user_map MODULE mapper/pam_user_map.c)
+ TARGET_LINK_LIBRARIES(pam_user_map pam)
+ SET_TARGET_PROPERTIES (pam_user_map PROPERTIES PREFIX "")
+ IF(INSTALL_PAMDIR)
+ INSTALL(TARGETS pam_user_map DESTINATION ${INSTALL_PAMDIR} COMPONENT Server)
+ INSTALL(FILES mapper/user_map.conf DESTINATION ${INSTALL_PAMDATADIR} COMPONENT Server)
+ SET(CPACK_RPM_server_USER_FILELIST ${CPACK_RPM_server_USER_FILELIST} "%config(noreplace) ${INSTALL_PAMDATADIRABS}/*" PARENT_SCOPE)
+ ENDIF()
+ ENDIF()
+ENDIF()
+
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/config_auth_pam.h)
diff --git a/plugin/auth_pam/auth_pam.c b/plugin/auth_pam/auth_pam.c
new file mode 100644
index 00000000..ffcfa019
--- /dev/null
+++ b/plugin/auth_pam/auth_pam.c
@@ -0,0 +1,241 @@
+/*
+ Copyright (c) 2011, 2020, MariaDB Corporation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+
+#include <my_global.h>
+#include <config_auth_pam.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <spawn.h>
+#include <mysql/plugin_auth.h>
+#include "auth_pam_tool.h"
+
+#ifndef DBUG_OFF
+static char pam_debug = 0;
+#define PAM_DEBUG(X) do { if (pam_debug) { fprintf X; } } while(0)
+#else
+#define PAM_DEBUG(X) /* no-op */
+#endif
+
+static char winbind_hack = 0;
+
+static char *opt_plugin_dir; /* To be dynamically linked. */
+static const char *tool_name= "auth_pam_tool_dir/auth_pam_tool";
+static const int tool_name_len= 31;
+
+/*
+ sleep_limit is now 5 meaning up to 1 second sleep.
+ each step means 10 times longer sleep, so 6 would mean 10 seconds.
+*/
+static const unsigned int sleep_limit= 5;
+
+static int pam_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
+{
+ int p_to_c[2], c_to_p[2]; /* Parent-to-child and child-to-parent pipes. */
+ pid_t proc_id;
+ int result= CR_ERROR, pkt_len= 0;
+ unsigned char field, *pkt;
+ unsigned int n_sleep= 0;
+ useconds_t sleep_time= 100;
+ posix_spawn_file_actions_t file_actions;
+ char toolpath[FN_REFLEN];
+ size_t plugin_dir_len= strlen(opt_plugin_dir);
+ char *const argv[2]= {toolpath, 0};
+ int res;
+
+ PAM_DEBUG((stderr, "PAM: opening pipes.\n"));
+ if (pipe(p_to_c) < 0 || pipe(c_to_p) < 0)
+ {
+ my_printf_error(ENOEXEC, "pam: cannot create pipes (errno: %M)",
+ ME_ERROR_LOG_ONLY, errno);
+ return CR_ERROR;
+ }
+
+ if (plugin_dir_len + tool_name_len + 2 > sizeof(toolpath))
+ {
+ my_printf_error(ENOEXEC, "pam: too long path to <plugindir>/%s",
+ ME_ERROR_LOG_ONLY, tool_name);
+ return CR_ERROR;
+ }
+
+ memcpy(toolpath, opt_plugin_dir, plugin_dir_len);
+ if (plugin_dir_len && toolpath[plugin_dir_len-1] != FN_LIBCHAR)
+ toolpath[plugin_dir_len++]= FN_LIBCHAR;
+ memcpy(toolpath+plugin_dir_len, tool_name, tool_name_len+1);
+
+ PAM_DEBUG((stderr, "PAM: forking %s\n", toolpath));
+ res= posix_spawn_file_actions_init(&file_actions) ||
+ posix_spawn_file_actions_addclose(&file_actions, p_to_c[1]) ||
+ posix_spawn_file_actions_addclose(&file_actions, c_to_p[0]) ||
+ posix_spawn_file_actions_adddup2(&file_actions, p_to_c[0], 0) ||
+ posix_spawn_file_actions_adddup2(&file_actions, c_to_p[1], 1) ||
+ posix_spawn(&proc_id, toolpath, &file_actions, NULL, argv, NULL);
+
+ /* Parent process continues. */
+ posix_spawn_file_actions_destroy(&file_actions);
+ close(p_to_c[0]);
+ close(c_to_p[1]);
+
+ if (res)
+ {
+ my_printf_error(ENOEXEC, "pam: cannot exec %s (errno: %M)",
+ ME_ERROR_LOG_ONLY, toolpath, errno);
+ goto error_ret;
+ }
+
+ /* no user name yet ? read the client handshake packet with the user name */
+ if (info->user_name == 0)
+ {
+ if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
+ goto error_ret;
+ }
+ else
+ pkt= NULL;
+
+ PAM_DEBUG((stderr, "PAM: parent sends user data [%s], [%s].\n",
+ info->user_name, info->auth_string));
+
+#ifndef DBUG_OFF
+ field= pam_debug ? 1 : 0;
+#else
+ field= 0;
+#endif
+ field|= winbind_hack ? 2 : 0;
+
+ if (write(p_to_c[1], &field, 1) != 1 ||
+ write_string(p_to_c[1], (const uchar *) info->user_name,
+ info->user_name_length) ||
+ write_string(p_to_c[1], (const uchar *) info->auth_string,
+ info->auth_string_length))
+ goto error_ret;
+
+ for (;;)
+ {
+ PAM_DEBUG((stderr, "PAM: listening to the sandbox.\n"));
+ if (read(c_to_p[0], &field, 1) < 1)
+ {
+ PAM_DEBUG((stderr, "PAM: read failed.\n"));
+ goto error_ret;
+ }
+
+ if (field == AP_EOF)
+ {
+ PAM_DEBUG((stderr, "PAM: auth OK returned.\n"));
+ break;
+ }
+
+ switch (field)
+ {
+ case AP_AUTHENTICATED_AS:
+ PAM_DEBUG((stderr, "PAM: reading authenticated_as string.\n"));
+ if (read_string(c_to_p[0], info->authenticated_as,
+ sizeof(info->authenticated_as) - 1) < 0)
+ goto error_ret;
+ break;
+
+ case AP_CONV:
+ {
+ unsigned char buf[10240];
+ int buf_len;
+
+ PAM_DEBUG((stderr, "PAM: getting CONV string.\n"));
+ if ((buf_len= read_string(c_to_p[0], (char *) buf, sizeof(buf))) < 0)
+ goto error_ret;
+
+ if (!pkt || !*pkt || (buf[0] >> 1) != 2)
+ {
+ PAM_DEBUG((stderr, "PAM: sending CONV string.\n"));
+ if (vio->write_packet(vio, buf, buf_len))
+ goto error_ret;
+
+ PAM_DEBUG((stderr, "PAM: reading CONV answer.\n"));
+ if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
+ goto error_ret;
+ }
+
+ PAM_DEBUG((stderr, "PAM: answering CONV.\n"));
+ if (write_string(p_to_c[1], pkt, pkt_len))
+ goto error_ret;
+
+ pkt= NULL;
+ }
+ break;
+
+ default:
+ PAM_DEBUG((stderr, "PAM: unknown sandbox field.\n"));
+ goto error_ret;
+ }
+ }
+ result= CR_OK;
+
+error_ret:
+ close(p_to_c[1]);
+ close(c_to_p[0]);
+ while (waitpid(proc_id, NULL, WNOHANG) != (int) proc_id)
+ {
+ if (n_sleep++ == sleep_limit)
+ {
+ /*
+ The auth_pam_tool application doesn't terminate.
+ Means something wrong happened there like pam_xxx.so hanged.
+ */
+ kill(proc_id, SIGKILL);
+ sleep_time= 1000000; /* 1 second wait should be enough. */
+ PAM_DEBUG((stderr, "PAM: auth_pam_tool doesn't terminate,"
+ " have to kill it.\n"));
+ }
+ else if (n_sleep > sleep_limit)
+ break;
+ usleep(sleep_time);
+ sleep_time*= 10;
+ }
+
+ PAM_DEBUG((stderr, "PAM: auth result %d.\n", result));
+ return result;
+}
+
+
+#include "auth_pam_common.c"
+
+
+static int init(void *p __attribute__((unused)))
+{
+ if (use_cleartext_plugin)
+ info.client_auth_plugin= "mysql_clear_password";
+ if (!(opt_plugin_dir= dlsym(RTLD_DEFAULT, "opt_plugin_dir")))
+ return 1;
+ return 0;
+}
+
+maria_declare_plugin(pam)
+{
+ MYSQL_AUTHENTICATION_PLUGIN,
+ &info,
+ "pam",
+ "MariaDB Corp",
+ "PAM based authentication",
+ PLUGIN_LICENSE_GPL,
+ init,
+ NULL,
+ 0x0200,
+ NULL,
+ vars,
+ "2.0",
+ MariaDB_PLUGIN_MATURITY_STABLE
+}
+maria_declare_plugin_end;
diff --git a/plugin/auth_pam/auth_pam_base.c b/plugin/auth_pam/auth_pam_base.c
new file mode 100644
index 00000000..1e8f4a08
--- /dev/null
+++ b/plugin/auth_pam/auth_pam_base.c
@@ -0,0 +1,179 @@
+/*
+ Copyright (c) 2011, 2018 MariaDB Corporation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+/*
+ This file contains code to interact with the PAM module.
+ To be included into auth_pam_tool.c and auth_pam_v2.c,
+
+ Before the #include these sould be defined:
+
+ struct param {
+ unsigned char buf[10240], *ptr;
+ MYSQL_PLUGIN_VIO *vio;
+ ... other arbitrary fields allowed.
+ };
+ static int write_packet(struct param *param, const unsigned char *buf,
+ int buf_len)
+ static int read_packet(struct param *param, unsigned char **pkt)
+*/
+
+#include <config_auth_pam.h>
+#include <stdio.h>
+#include <string.h>
+#include <security/pam_appl.h>
+#include <security/pam_modules.h>
+
+/* It least solaris doesn't have strndup */
+
+#ifndef HAVE_STRNDUP
+char *strndup(const char *from, size_t length)
+{
+ char *ptr;
+ size_t max_length= strlen(from);
+ if (length > max_length)
+ length= max_length;
+ if ((ptr= (char*) malloc(length+1)) != 0)
+ {
+ memcpy((char*) ptr, (char*) from, length);
+ ptr[length]=0;
+ }
+ return ptr;
+}
+#endif
+
+#ifndef DBUG_OFF
+static char pam_debug = 0;
+#define PAM_DEBUG(X) do { if (pam_debug) { fprintf X; } } while(0)
+#else
+#define PAM_DEBUG(X) /* no-op */
+#endif
+
+static char winbind_hack = 0;
+
+static int conv(int n, const struct pam_message **msg,
+ struct pam_response **resp, void *data)
+{
+ struct param *param = (struct param *)data;
+ unsigned char *end = param->buf + sizeof(param->buf) - 1;
+ int i;
+
+ *resp = 0;
+
+ for (i = 0; i < n; i++)
+ {
+ /* if there's a message - append it to the buffer */
+ if (msg[i]->msg)
+ {
+ int len = strlen(msg[i]->msg);
+ if (len > end - param->ptr)
+ len = end - param->ptr;
+ if (len > 0)
+ {
+ memcpy(param->ptr, msg[i]->msg, len);
+ param->ptr+= len;
+ *(param->ptr)++ = '\n';
+ }
+ }
+ /* if the message style is *_PROMPT_*, meaning PAM asks a question,
+ send the accumulated text to the client, read the reply */
+ if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF ||
+ msg[i]->msg_style == PAM_PROMPT_ECHO_ON)
+ {
+ int pkt_len;
+ unsigned char *pkt;
+
+ /* allocate the response array.
+ freeing it is the responsibility of the caller */
+ if (*resp == 0)
+ {
+ *resp = calloc(sizeof(struct pam_response), n);
+ if (*resp == 0)
+ return PAM_BUF_ERR;
+ }
+
+ /* dialog plugin interprets the first byte of the packet
+ as the magic number.
+ 2 means "read the input with the echo enabled"
+ 4 means "password-like input, echo disabled"
+ C'est la vie. */
+ param->buf[0] = msg[i]->msg_style == PAM_PROMPT_ECHO_ON ? 2 : 4;
+ PAM_DEBUG((stderr, "PAM: conv: send(%.*s)\n",
+ (int)(param->ptr - param->buf - 1), param->buf));
+ pkt_len= roundtrip(param, param->buf, param->ptr - param->buf - 1, &pkt);
+ if (pkt_len < 0)
+ return PAM_CONV_ERR;
+
+ PAM_DEBUG((stderr, "PAM: conv: recv(%.*s)\n", pkt_len, pkt));
+ /* allocate and copy the reply to the response array */
+ if (!((*resp)[i].resp= strndup((char*) pkt, pkt_len)))
+ return PAM_CONV_ERR;
+ param->ptr = param->buf + 1;
+ }
+ }
+ return PAM_SUCCESS;
+}
+
+#define DO(X) if ((status = (X)) != PAM_SUCCESS) goto end
+
+#if defined(SOLARIS) || defined(__sun)
+typedef void** pam_get_item_3_arg;
+#else
+typedef const void** pam_get_item_3_arg;
+#endif
+
+static int pam_auth_base(struct param *param, MYSQL_SERVER_AUTH_INFO *info)
+{
+ pam_handle_t *pamh = NULL;
+ int status;
+ const char *new_username= NULL;
+ /* The following is written in such a way to make also solaris happy */
+ struct pam_conv pam_start_arg = { &conv, (char*) param };
+
+ /*
+ get the service name, as specified in
+
+ CREATE USER ... IDENTIFIED WITH pam AS "service"
+ */
+ const char *service = info->auth_string && info->auth_string[0]
+ ? info->auth_string : "mysql";
+
+ param->ptr = param->buf + 1;
+
+ PAM_DEBUG((stderr, "PAM: pam_start(%s, %s)\n", service, info->user_name));
+ DO( pam_start(service, info->user_name, &pam_start_arg, &pamh) );
+
+ PAM_DEBUG((stderr, "PAM: pam_authenticate(0)\n"));
+ DO( pam_authenticate (pamh, 0) );
+
+ PAM_DEBUG((stderr, "PAM: pam_acct_mgmt(0)\n"));
+ DO( pam_acct_mgmt(pamh, 0) );
+
+ PAM_DEBUG((stderr, "PAM: pam_get_item(PAM_USER)\n"));
+ DO( pam_get_item(pamh, PAM_USER, (pam_get_item_3_arg) &new_username) );
+
+ if (new_username &&
+ (winbind_hack ? strcasecmp : strcmp)(new_username, info->user_name))
+ strncpy(info->authenticated_as, new_username,
+ sizeof(info->authenticated_as));
+ info->authenticated_as[sizeof(info->authenticated_as)-1]= 0;
+
+end:
+ PAM_DEBUG((stderr, "PAM: status = %d (%s) user = %s\n",
+ status, pam_strerror(pamh, status), info->authenticated_as));
+ pam_end(pamh, status);
+ return status == PAM_SUCCESS ? CR_OK : CR_ERROR;
+}
+
diff --git a/plugin/auth_pam/auth_pam_common.c b/plugin/auth_pam/auth_pam_common.c
new file mode 100644
index 00000000..ef8f0f65
--- /dev/null
+++ b/plugin/auth_pam/auth_pam_common.c
@@ -0,0 +1,56 @@
+/*
+ Copyright (c) 2011, 2018 MariaDB Corporation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+/*
+ In this file we gather the plugin interface definitions
+ that are same in all the PAM plugin versions.
+ To be included into auth_pam.c and auth_pam_v1.c.
+*/
+
+static struct st_mysql_auth info =
+{
+ MYSQL_AUTHENTICATION_INTERFACE_VERSION,
+ "dialog",
+ pam_auth,
+ NULL, NULL /* no PASSWORD() */
+};
+
+static char use_cleartext_plugin;
+static MYSQL_SYSVAR_BOOL(use_cleartext_plugin, use_cleartext_plugin,
+ PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_READONLY,
+ "Use mysql_cleartext_plugin on the client side instead of the dialog "
+ "plugin. This may be needed for compatibility reasons, but it only "
+ "supports simple PAM policies that don't require anything besides "
+ "a password", NULL, NULL, 0);
+
+static MYSQL_SYSVAR_BOOL(winbind_workaround, winbind_hack, PLUGIN_VAR_OPCMDARG,
+ "Compare usernames case insensitively to work around pam_winbind "
+ "unconditional username lowercasing", NULL, NULL, 0);
+
+#ifndef DBUG_OFF
+static MYSQL_SYSVAR_BOOL(debug, pam_debug, PLUGIN_VAR_OPCMDARG,
+ "Log all PAM activity", NULL, NULL, 0);
+#endif
+
+
+static struct st_mysql_sys_var* vars[] = {
+ MYSQL_SYSVAR(use_cleartext_plugin),
+ MYSQL_SYSVAR(winbind_workaround),
+#ifndef DBUG_OFF
+ MYSQL_SYSVAR(debug),
+#endif
+ NULL
+};
diff --git a/plugin/auth_pam/auth_pam_tool.c b/plugin/auth_pam/auth_pam_tool.c
new file mode 100644
index 00000000..225f35a6
--- /dev/null
+++ b/plugin/auth_pam/auth_pam_tool.c
@@ -0,0 +1,120 @@
+/*
+ Copyright (c) 2011, 2018 MariaDB Corporation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <mysql/plugin_auth_common.h>
+
+struct param {
+ unsigned char buf[10240], *ptr;
+};
+
+
+#include "auth_pam_tool.h"
+
+
+static int roundtrip(struct param *param, const unsigned char *buf,
+ int buf_len, unsigned char **pkt)
+{
+ unsigned char b= AP_CONV;
+ if (write(1, &b, 1) < 1 || write_string(1, buf, buf_len))
+ return -1;
+ *pkt= (unsigned char *) param->buf;
+ return read_string(0, (char *) param->buf, (int) sizeof(param->buf));
+}
+
+typedef struct st_mysql_server_auth_info
+{
+ /**
+ User name as sent by the client and shown in USER().
+ NULL if the client packet with the user name was not received yet.
+ */
+ char *user_name;
+
+ /**
+ A corresponding column value from the mysql.user table for the
+ matching account name
+ */
+ char *auth_string;
+
+ /**
+ Matching account name as found in the mysql.user table.
+ A plugin can override it with another name that will be
+ used by MySQL for authorization, and shown in CURRENT_USER()
+ */
+ char authenticated_as[MYSQL_USERNAME_LENGTH+1];
+} MYSQL_SERVER_AUTH_INFO;
+
+
+#include "auth_pam_base.c"
+
+
+int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
+{
+ struct param param;
+ MYSQL_SERVER_AUTH_INFO info;
+ unsigned char field;
+ int res;
+ char a_buf[MYSQL_USERNAME_LENGTH + 1 + 1024];
+
+ if ((res= setreuid(0, 0)))
+ fprintf(stderr, "Got error %d from setreuid()\n", (int) errno);
+
+ if (read(0, &field, 1) < 1)
+ return -1;
+#ifndef DBUG_OFF
+ pam_debug= field & 1;
+#endif
+ winbind_hack= field & 2;
+
+ PAM_DEBUG((stderr, "PAM: sandbox started [%s].\n", argv[0]));
+
+ info.user_name= a_buf;
+ if ((res= read_string(0, info.user_name, sizeof(a_buf))) < 0)
+ return -1;
+ PAM_DEBUG((stderr, "PAM: sandbox username [%s].\n", info.user_name));
+
+ info.auth_string= info.user_name + res + 1;
+ if (read_string(0, info.auth_string, sizeof(a_buf) - 1 - res) < 0)
+ return -1;
+
+ PAM_DEBUG((stderr, "PAM: sandbox auth string [%s].\n", info.auth_string));
+
+ if ((res= pam_auth_base(&param, &info)) != CR_OK)
+ {
+ PAM_DEBUG((stderr, "PAM: auth failed, sandbox closed.\n"));
+ return -1;
+ }
+
+ if (info.authenticated_as[0])
+ {
+ PAM_DEBUG((stderr, "PAM: send authenticated_as field.\n"));
+ field= AP_AUTHENTICATED_AS;
+ if (write(1, &field, 1) < 1 ||
+ write_string(1, (unsigned char *) info.authenticated_as,
+ strlen(info.authenticated_as)))
+ return -1;
+ }
+
+ PAM_DEBUG((stderr, "PAM: send OK result.\n"));
+ field= AP_EOF;
+ if (write(1, &field, 1) != 1)
+ return -1;
+
+ PAM_DEBUG((stderr, "PAM: sandbox closed.\n"));
+ return 0;
+}
diff --git a/plugin/auth_pam/auth_pam_tool.h b/plugin/auth_pam/auth_pam_tool.h
new file mode 100644
index 00000000..60ae016d
--- /dev/null
+++ b/plugin/auth_pam/auth_pam_tool.h
@@ -0,0 +1,81 @@
+/*
+ Copyright (c) 2011, 2018 MariaDB Corporation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+/*
+ This file contains definitions and functions for
+ the interface between the auth_pam.so (PAM plugin version 2)
+ and the auth_pam_tool executable.
+ To be included both in auth_pam.c and auth_pam_tool.c.
+*/
+
+#define AP_AUTHENTICATED_AS 'A'
+#define AP_CONV 'C'
+#define AP_EOF 'E'
+
+
+static int read_length(int file)
+{
+ unsigned char hdr[2];
+
+ if (read(file, hdr, 2) < 2)
+ return -1;
+
+ return (((int) hdr[0]) << 8) + (int) hdr[1];
+}
+
+
+static void store_length(int len, unsigned char *p_len)
+{
+ p_len[0]= (unsigned char) ((len >> 8) & 0xFF);
+ p_len[1]= (unsigned char) (len & 0xFF);
+}
+
+
+/*
+ Returns the length of the string read,
+ or -1 on error.
+*/
+
+static int read_string(int file, char *s, int s_size)
+{
+ int len;
+
+ len= read_length(file);
+
+ if (len < 0 || len > s_size-1 ||
+ read(file, s, len) < len)
+ return -1;
+
+ s[len]= 0;
+
+ return len;
+}
+
+
+/*
+ Returns 0 on success.
+*/
+
+static int write_string(int file, const unsigned char *s, int s_len)
+{
+ unsigned char hdr[2];
+ store_length(s_len, hdr);
+ return write(file, hdr, 2) < 2 ||
+ write(file, s, s_len) < s_len;
+}
+
+
+#define MAX_PAM_SERVICE_NAME 1024
diff --git a/plugin/auth_pam/auth_pam_v1.c b/plugin/auth_pam/auth_pam_v1.c
new file mode 100644
index 00000000..a38ef8f5
--- /dev/null
+++ b/plugin/auth_pam/auth_pam_v1.c
@@ -0,0 +1,86 @@
+/*
+ Copyright (c) 2011, 2018 MariaDB Corporation
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */
+
+#include <mysql/plugin_auth.h>
+
+struct param {
+ unsigned char buf[10240], *ptr, *cached;
+ int cached_len;
+ MYSQL_PLUGIN_VIO *vio;
+};
+
+static int roundtrip(struct param *param, const unsigned char *buf,
+ int buf_len, unsigned char **pkt)
+{
+ if (param->cached && *param->cached && (buf[0] >> 1) == 2)
+ {
+ *pkt= param->cached;
+ param->cached= NULL;
+ return param->cached_len;
+ }
+ param->cached= NULL;
+ if (param->vio->write_packet(param->vio, buf, buf_len))
+ return -1;
+ return param->vio->read_packet(param->vio, pkt);
+}
+
+#include "auth_pam_base.c"
+
+static int pam_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
+{
+ struct param param;
+ param.vio = vio;
+
+ /* no user name yet ? read the client handshake packet with the user name */
+ if (info->user_name == 0)
+ {
+ if ((param.cached_len= vio->read_packet(vio, &param.cached)) < 0)
+ return CR_ERROR;
+ }
+ else
+ param.cached= NULL;
+
+ return pam_auth_base(&param, info);
+}
+
+
+#include "auth_pam_common.c"
+
+
+static int init(void *p __attribute__((unused)))
+{
+ if (use_cleartext_plugin)
+ info.client_auth_plugin= "mysql_clear_password";
+ return 0;
+}
+
+maria_declare_plugin(pam)
+{
+ MYSQL_AUTHENTICATION_PLUGIN,
+ &info,
+ "pam",
+ "Sergei Golubchik",
+ "PAM based authentication",
+ PLUGIN_LICENSE_GPL,
+ init,
+ NULL,
+ 0x0100,
+ NULL,
+ vars,
+ "1.0",
+ MariaDB_PLUGIN_MATURITY_STABLE
+}
+maria_declare_plugin_end;
diff --git a/plugin/auth_pam/config.h.cmake b/plugin/auth_pam/config.h.cmake
new file mode 100644
index 00000000..2a60e99d
--- /dev/null
+++ b/plugin/auth_pam/config.h.cmake
@@ -0,0 +1,5 @@
+#cmakedefine HAVE_POSIX_GETGROUPLIST 1
+#cmakedefine HAVE_PAM_SYSLOG 1
+#cmakedefine HAVE_PAM_EXT_H 1
+#cmakedefine HAVE_PAM_APPL_H 1
+#cmakedefine HAVE_STRNDUP 1
diff --git a/plugin/auth_pam/mapper/pam_user_map.c b/plugin/auth_pam/mapper/pam_user_map.c
new file mode 100644
index 00000000..5dda97a2
--- /dev/null
+++ b/plugin/auth_pam/mapper/pam_user_map.c
@@ -0,0 +1,277 @@
+/*
+ Pam module to change user names arbitrarily in the pam stack.
+
+ Compile as
+
+ gcc pam_user_map.c -shared -lpam -fPIC -o pam_user_map.so
+
+ Install as appropriate (for example, in /lib/security/).
+ Add to your /etc/pam.d/mysql (preferably, at the end) this line:
+=========================================================
+auth required pam_user_map.so
+=========================================================
+
+ And create /etc/security/user_map.conf with the desired mapping
+ in the format: orig_user_name: mapped_user_name
+ @user's_group_name: mapped_user_name
+=========================================================
+#comments and empty lines are ignored
+john: jack
+bob: admin
+top: accounting
+@group_ro: readonly
+=========================================================
+
+If something doesn't work as expected you can get verbose
+comments with the 'debug' option like this
+=========================================================
+auth required pam_user_map.so debug
+=========================================================
+These comments are written to the syslog as 'authpriv.debug'
+and usually end up in /var/log/secure file.
+*/
+
+#include <config_auth_pam.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <syslog.h>
+#include <grp.h>
+#include <pwd.h>
+
+#ifdef HAVE_PAM_EXT_H
+#include <security/pam_ext.h>
+#endif
+
+#ifdef HAVE_PAM_APPL_H
+#include <unistd.h>
+#include <security/pam_appl.h>
+#endif
+
+#include <security/pam_modules.h>
+
+#ifndef HAVE_PAM_SYSLOG
+#include <stdarg.h>
+static void
+pam_syslog (const pam_handle_t *pamh, int priority,
+ const char *fmt, ...)
+{
+ va_list args;
+ va_start (args, fmt);
+ vsyslog (priority, fmt, args);
+ va_end (args);
+}
+#endif
+
+#define FILENAME "/etc/security/user_map.conf"
+#define skip(what) while (*s && (what)) s++
+#define SYSLOG_DEBUG if (mode_debug) pam_syslog
+
+#define GROUP_BUFFER_SIZE 100
+static const char debug_keyword[]= "debug";
+
+#ifdef HAVE_POSIX_GETGROUPLIST
+typedef gid_t my_gid_t;
+#else
+typedef int my_gid_t;
+#endif
+
+static int populate_user_groups(const char *user, my_gid_t **groups)
+{
+ my_gid_t user_group_id;
+ my_gid_t *loc_groups= *groups;
+ int ng;
+
+ {
+ struct passwd *pw= getpwnam(user);
+ if (!pw)
+ return 0;
+ user_group_id= pw->pw_gid;
+ }
+
+ ng= GROUP_BUFFER_SIZE;
+ if (getgrouplist(user, user_group_id, loc_groups, &ng) < 0)
+ {
+ /* The rare case when the user is present in more than */
+ /* GROUP_BUFFER_SIZE groups. */
+ loc_groups= (my_gid_t *) malloc(ng * sizeof (my_gid_t));
+
+ if (!loc_groups)
+ return 0;
+
+ (void) getgrouplist(user, user_group_id, loc_groups, &ng);
+ *groups= (my_gid_t*)loc_groups;
+ }
+
+ return ng;
+}
+
+
+static int user_in_group(const my_gid_t *user_groups, int ng,const char *group)
+{
+ my_gid_t group_id;
+ const my_gid_t *groups_end = user_groups + ng;
+
+ {
+ struct group *g= getgrnam(group);
+ if (!g)
+ return 0;
+ group_id= g->gr_gid;
+ }
+
+ for (; user_groups < groups_end; user_groups++)
+ {
+ if (*user_groups == group_id)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static void print_groups(pam_handle_t *pamh, const my_gid_t *user_groups, int ng)
+{
+ char buf[256];
+ char *c_buf= buf, *buf_end= buf+sizeof(buf)-2;
+ struct group *gr;
+ int cg;
+
+ for (cg=0; cg < ng; cg++)
+ {
+ char *c;
+ if (c_buf == buf_end)
+ break;
+ *(c_buf++)= ',';
+ if (!(gr= getgrgid(user_groups[cg])) ||
+ !(c= gr->gr_name))
+ continue;
+ while (*c)
+ {
+ if (c_buf == buf_end)
+ break;
+ *(c_buf++)= *(c++);
+ }
+ }
+ c_buf[0]= c_buf[1]= 0;
+ pam_syslog(pamh, LOG_DEBUG, "User belongs to %d %s [%s].\n",
+ ng, (ng == 1) ? "group" : "groups", buf+1);
+}
+
+int pam_sm_authenticate(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+ int mode_debug= 0;
+ int pam_err, line= 0;
+ const char *username;
+ char buf[256];
+ FILE *f;
+ my_gid_t group_buffer[GROUP_BUFFER_SIZE];
+ my_gid_t *groups= group_buffer;
+ int n_groups= -1;
+
+ for (; argc > 0; argc--)
+ {
+ if (strcasecmp(argv[argc-1], debug_keyword) == 0)
+ mode_debug= 1;
+ }
+
+ SYSLOG_DEBUG(pamh, LOG_DEBUG, "Opening file '%s'.\n", FILENAME);
+
+ f= fopen(FILENAME, "r");
+ if (f == NULL)
+ {
+ pam_syslog(pamh, LOG_ERR, "Cannot open '%s'\n", FILENAME);
+ return PAM_SYSTEM_ERR;
+ }
+
+ pam_err = pam_get_item(pamh, PAM_USER, (const void**)&username);
+ if (pam_err != PAM_SUCCESS)
+ {
+ pam_syslog(pamh, LOG_ERR, "Cannot get username.\n");
+ goto ret;
+ }
+
+ SYSLOG_DEBUG(pamh, LOG_DEBUG, "Incoming username '%s'.\n", username);
+
+ while (fgets(buf, sizeof(buf), f) != NULL)
+ {
+ char *s= buf, *from, *to, *end_from, *end_to;
+ int check_group;
+ int cmp_result;
+
+ line++;
+
+ skip(isspace(*s));
+ if (*s == '#' || *s == 0) continue;
+ if ((check_group= *s == '@'))
+ {
+ if (n_groups < 0)
+ {
+ n_groups= populate_user_groups(username, &groups);
+ if (mode_debug)
+ print_groups(pamh, groups, n_groups);
+ }
+ s++;
+ }
+ from= s;
+ skip(isalnum(*s) || (*s == '_') || (*s == '.') || (*s == '-') ||
+ (*s == '$') || (*s == '\\') || (*s == '/') || (*s == '@'));
+ end_from= s;
+ skip(isspace(*s));
+ if (end_from == from || *s++ != ':') goto syntax_error;
+ skip(isspace(*s));
+ to= s;
+ skip(isalnum(*s) || (*s == '_') || (*s == '.') || (*s == '-') ||
+ (*s == '$'));
+ end_to= s;
+ if (end_to == to) goto syntax_error;
+
+ *end_from= *end_to= 0;
+
+ if (check_group)
+ {
+ cmp_result= user_in_group(groups, n_groups, from);
+ SYSLOG_DEBUG(pamh, LOG_DEBUG, "Check if user is in group '%s': %s\n",
+ from, cmp_result ? "YES":"NO");
+ }
+ else
+ {
+ cmp_result= (strcmp(username, from) == 0);
+ SYSLOG_DEBUG(pamh, LOG_DEBUG, "Check if username '%s': %s\n",
+ from, cmp_result ? "YES":"NO");
+ }
+ if (cmp_result)
+ {
+ pam_err= pam_set_item(pamh, PAM_USER, to);
+ SYSLOG_DEBUG(pamh, LOG_DEBUG,
+ (pam_err == PAM_SUCCESS) ? "User mapped as '%s'\n" :
+ "Couldn't map as '%s'\n", to);
+ goto ret;
+ }
+ }
+
+ SYSLOG_DEBUG(pamh, LOG_DEBUG, "User not found in the list.\n");
+ pam_err= PAM_AUTH_ERR;
+ goto ret;
+
+syntax_error:
+ pam_syslog(pamh, LOG_ERR, "Syntax error at %s:%d", FILENAME, line);
+ pam_err= PAM_SYSTEM_ERR;
+ret:
+ if (groups != group_buffer)
+ free(groups);
+
+ fclose(f);
+
+ return pam_err;
+}
+
+
+int pam_sm_setcred(pam_handle_t *pamh, int flags,
+ int argc, const char *argv[])
+{
+
+ return PAM_SUCCESS;
+}
+
diff --git a/plugin/auth_pam/mapper/user_map.conf b/plugin/auth_pam/mapper/user_map.conf
new file mode 100644
index 00000000..4af8fb0f
--- /dev/null
+++ b/plugin/auth_pam/mapper/user_map.conf
@@ -0,0 +1,13 @@
+#
+# Configuration file for pam_user_map.so
+#
+# defines mapping in the form
+#
+# orig_user_name: mapped_user_name
+#
+# or (to map all users in a specific group)
+#
+# @group_name: mapped_user_name
+#
+# comments and empty lines are ignored
+#
diff --git a/plugin/auth_pam/testing/CMakeLists.txt b/plugin/auth_pam/testing/CMakeLists.txt
new file mode 100644
index 00000000..151823b9
--- /dev/null
+++ b/plugin/auth_pam/testing/CMakeLists.txt
@@ -0,0 +1,15 @@
+# gcc pam_mariadb_mtr.c -shared -lpam -fPIC -o pam_mariadb_mtr.so
+
+ADD_LIBRARY(pam_mariadb_mtr MODULE pam_mariadb_mtr.c)
+SET_TARGET_PROPERTIES (pam_mariadb_mtr PROPERTIES PREFIX "")
+TARGET_LINK_LIBRARIES(pam_mariadb_mtr pam)
+
+IF(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ SET_SOURCE_FILES_PROPERTIES(
+ pam_mariadb_mtr.c
+ PROPERTY COMPILE_FLAGS "-Wno-incompatible-pointer-types-discards-qualifiers")
+ENDIF()
+
+SET(dest DESTINATION "${INSTALL_MYSQLTESTDIR}/suite/plugins/pam" COMPONENT Test)
+INSTALL(TARGETS pam_mariadb_mtr ${dest})
+INSTALL(FILES mariadb_mtr.conf RENAME mariadb_mtr ${dest})
diff --git a/plugin/auth_pam/testing/mariadb_mtr.conf b/plugin/auth_pam/testing/mariadb_mtr.conf
new file mode 100644
index 00000000..241afb43
--- /dev/null
+++ b/plugin/auth_pam/testing/mariadb_mtr.conf
@@ -0,0 +1,4 @@
+# Put it in /etc/pam.d/mariadb_mtr
+
+auth required pam_mariadb_mtr.so pam_test
+account required pam_permit.so
diff --git a/plugin/auth_pam/testing/pam_mariadb_mtr.c b/plugin/auth_pam/testing/pam_mariadb_mtr.c
new file mode 100644
index 00000000..108aeb94
--- /dev/null
+++ b/plugin/auth_pam/testing/pam_mariadb_mtr.c
@@ -0,0 +1,84 @@
+/*
+ This code is in the public domain and has no copyright.
+
+ Pam module to test pam authentication plugin. Used in pam tests.
+ Linux only.
+
+ Install as appropriate (for example, in /lib/security/).
+ see also mariadb_mtr.conf
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <security/pam_modules.h>
+#include <security/pam_appl.h>
+
+#define N 3
+
+int pam_sm_authenticate(pam_handle_t *pamh, int flags __attribute__((unused)),
+ int argc, const char *argv[])
+{
+ struct pam_conv *conv;
+ struct pam_response *resp = 0;
+ int pam_err, retval = PAM_SYSTEM_ERR;
+ struct pam_message msg[N] = {
+ { PAM_TEXT_INFO, (char*)"Challenge input first." },
+ { PAM_PROMPT_ECHO_OFF, (char*)"Enter:" },
+ { PAM_ERROR_MSG, (char*)"Now, the magic number!" }
+ };
+ const struct pam_message *msgp[N] = { msg, msg+1, msg+2 };
+ char *r1 = 0, *r2 = 0;
+
+ pam_err = pam_get_item(pamh, PAM_CONV, (const void **)&conv);
+ if (pam_err != PAM_SUCCESS)
+ goto ret;
+
+ pam_err = (*conv->conv)(N, msgp, &resp, conv->appdata_ptr);
+
+ if (pam_err != PAM_SUCCESS || !resp || !((r1= resp[1].resp)))
+ goto ret;
+
+ if (strcmp(r1, "cleartext good") == 0)
+ retval = PAM_SUCCESS;
+ else if (strcmp(r1, "cleartext bad") == 0)
+ retval = PAM_AUTH_ERR;
+ else
+ {
+ free(resp);
+ resp= NULL;
+ msg[0].msg_style = PAM_PROMPT_ECHO_ON;
+ msg[0].msg = (char*)"PIN:";
+ pam_err = (*conv->conv)(1, msgp, &resp, conv->appdata_ptr);
+
+ if (pam_err != PAM_SUCCESS || !resp || !((r2= resp[0].resp)))
+ goto ret;
+
+ /* Produce the crash for testing purposes. */
+ if (strcmp(r1, "crash pam module") == 0 && atoi(r2) == 616)
+ abort();
+
+ if (strlen(r1) == (size_t)atoi(r2) % 100)
+ retval = PAM_SUCCESS;
+ else
+ retval = PAM_AUTH_ERR;
+ }
+
+ if (argc > 0 && argv[0])
+ pam_set_item(pamh, PAM_USER, argv[0]);
+
+ret:
+ free(resp);
+ free(r1);
+ free(r2);
+ return retval;
+}
+
+int pam_sm_setcred(pam_handle_t *pamh __attribute__((unused)),
+ int flags __attribute__((unused)),
+ int argc __attribute__((unused)),
+ const char *argv[] __attribute__((unused)))
+{
+
+ return PAM_SUCCESS;
+}
+