diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:00:34 +0000 |
commit | 3f619478f796eddbba6e39502fe941b285dd97b1 (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /plugin/auth_pam | |
parent | Initial commit. (diff) | |
download | mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.tar.xz mariadb-3f619478f796eddbba6e39502fe941b285dd97b1.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugin/auth_pam')
-rw-r--r-- | plugin/auth_pam/CMakeLists.txt | 70 | ||||
-rw-r--r-- | plugin/auth_pam/auth_pam.c | 241 | ||||
-rw-r--r-- | plugin/auth_pam/auth_pam_base.c | 179 | ||||
-rw-r--r-- | plugin/auth_pam/auth_pam_common.c | 56 | ||||
-rw-r--r-- | plugin/auth_pam/auth_pam_tool.c | 120 | ||||
-rw-r--r-- | plugin/auth_pam/auth_pam_tool.h | 81 | ||||
-rw-r--r-- | plugin/auth_pam/auth_pam_v1.c | 86 | ||||
-rw-r--r-- | plugin/auth_pam/config.h.cmake | 5 | ||||
-rw-r--r-- | plugin/auth_pam/mapper/pam_user_map.c | 277 | ||||
-rw-r--r-- | plugin/auth_pam/mapper/user_map.conf | 13 | ||||
-rw-r--r-- | plugin/auth_pam/testing/CMakeLists.txt | 15 | ||||
-rw-r--r-- | plugin/auth_pam/testing/mariadb_mtr.conf | 4 | ||||
-rw-r--r-- | plugin/auth_pam/testing/pam_mariadb_mtr.c | 84 |
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(¶m, &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, ¶m.cached)) < 0) + return CR_ERROR; + } + else + param.cached= NULL; + + return pam_auth_base(¶m, 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; +} + |