From 159929181100c04167a6b8fce7a8efa54be67372 Mon Sep 17 00:00:00 2001 From: Przemyslaw Skibinski Date: Thu, 9 Jun 2022 20:39:49 +0200 Subject: [PATCH] [plugin] PS-269: Initial Percona Server 8.0.12 tree PS-5741: Incorrect use of memset_s in keyring_vault. Fixed the usage of memset_s. The arguments should be: void memset_s(void *dest, size_t dest_max, int c, size_t n) where the 2nd argument is size of buffer and the 3rd is argument is character to fill. --------------------------------------------------------------------------- PS-7769 - Fix use-after-return error in audit_log_exclude_accounts_validate --- *Problem:* `st_mysql_value::val_str` might return a pointer to `buf` which after the function called is deleted. Therefore the value in `save`, after reuturnin from the function, is invalid. In this particular case, the error is not manifesting as val_str` returns memory allocated with `thd_strmake` and it does not use `buf`. *Solution:* Allocate memory with `thd_strmake` so the memory in `save` is not local. --------------------------------------------------------------------------- Fix test main.bug12969156 when WITH_ASAN=ON *Problem:* ASAN complains about stack-buffer-overflow on function `mysql_heartbeat`: ``` ==90890==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fe746d06d14 at pc 0x7fe760f5b017 bp 0x7fe746d06cd0 sp 0x7fe746d06478 WRITE of size 24 at 0x7fe746d06d14 thread T16777215 Address 0x7fe746d06d14 is located in stack of thread T26 at offset 340 in frame #0 0x7fe746d0a55c in mysql_heartbeat(void*) /home/yura/ws/percona-server/plugin/daemon_example/daemon_example.cc:62 This frame has 4 object(s): [48, 56) 'result' (line 66) [80, 112) '_db_stack_frame_' (line 63) [144, 200) 'tm_tmp' (line 67) [240, 340) 'buffer' (line 65) <== Memory access at offset 340 overflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) Thread T26 created by T25 here: #0 0x7fe760f5f6d5 in __interceptor_pthread_create ../../../../src/libsanitizer/asan/asan_interceptors.cpp:216 #1 0x557ccbbcb857 in my_thread_create /home/yura/ws/percona-server/mysys/my_thread.c:104 #2 0x7fe746d0b21a in daemon_example_plugin_init /home/yura/ws/percona-server/plugin/daemon_example/daemon_example.cc:148 #3 0x557ccb4c69c7 in plugin_initialize /home/yura/ws/percona-server/sql/sql_plugin.cc:1279 #4 0x557ccb4d19cd in mysql_install_plugin /home/yura/ws/percona-server/sql/sql_plugin.cc:2279 #5 0x557ccb4d218f in Sql_cmd_install_plugin::execute(THD*) /home/yura/ws/percona-server/sql/sql_plugin.cc:4664 #6 0x557ccb47695e in mysql_execute_command(THD*, bool) /home/yura/ws/percona-server/sql/sql_parse.cc:5160 #7 0x557ccb47977c in mysql_parse(THD*, Parser_state*, bool) /home/yura/ws/percona-server/sql/sql_parse.cc:5952 #8 0x557ccb47b6c2 in dispatch_command(THD*, COM_DATA const*, enum_server_command) /home/yura/ws/percona-server/sql/sql_parse.cc:1544 #9 0x557ccb47de1d in do_command(THD*) /home/yura/ws/percona-server/sql/sql_parse.cc:1065 #10 0x557ccb6ac294 in handle_connection /home/yura/ws/percona-server/sql/conn_handler/connection_handler_per_thread.cc:325 #11 0x557ccbbfabb0 in pfs_spawn_thread /home/yura/ws/percona-server/storage/perfschema/pfs.cc:2198 #12 0x7fe760ab544f in start_thread nptl/pthread_create.c:473 ``` The reason is that `my_thread_cancel` is used to finish the daemon thread. This is not and orderly way of finishing the thread. ASAN does not register the stack variables are not used anymore which generates the error above. This is a benign error as all the variables are on the stack. *Solution*: Finish the thread in orderly way by using a signalling variable. --------------------------------------------------------------------------- PS-8204: Fix XML escape rules for audit plugin https://jira.percona.com/browse/PS-8204 There was a wrong length specified for some XML escape rules. As a result of this terminating null symbol from replacement rule was copied into resulting string. This lead to quer text truncation in audit log file. In addition added empty replacement rules for '\b' and 'f' symbols which just remove them from resulting string. These symboles are not supported in XML 1.0. --------------------------------------------------------------------------- PS-8854: Add main.percona_udf MTR test Add a test to check FNV1A_64, FNV_64, and MURMUR_HASH user-defined functions. --------------------------------------------------------------------------- PS-9218: Merge MySQL 8.4.0 (fix gcc-14 build) https://perconadev.atlassian.net/browse/PS-9218 --- include/my_bitmap.h | 4 +- include/my_sys.h | 3 + mysql-test/include/plugin.defs | 7 + mysql-test/r/percona_udf.result | 37 ++ mysql-test/t/percona_udf.test | 65 ++++ mysys/CMakeLists.txt | 2 + mysys/my_malloc.cc | 13 + plugin/daemon_example/daemon_example.cc | 7 +- .../xcom_network_provider_ssl_native_lib.cc | 5 + plugin/keyring/common/i_keyring_io.h | 2 +- plugin/keyring/common/i_keyring_key.h | 1 + plugin/keyring/common/keyring_key.cc | 12 +- plugin/keyring/common/keyring_key.h | 3 +- plugin/keyring/common/keyring_memory.h | 53 ++- plugin/keyring/common/secure_string.h | 34 ++ plugin/keyring/keyring_file.version | 8 + plugin/percona-pam-for-mysql/CMakeLists.txt | 42 +++ plugin/percona-pam-for-mysql/doc/make.bat | 170 +++++++++ .../_static/percona-pam-plugin-logo.jpg | Bin 0 -> 36163 bytes .../doc/source/_static/percona_favicon.ico | Bin 0 -> 894 bytes .../percona-pam-for-mysql/doc/source/conf.py | 245 +++++++++++++ .../percona-pam-for-mysql/doc/source/faq.rst | 51 +++ .../doc/source/glossary.rst | 8 + .../doc/source/index.rst | 51 +++ .../doc/source/installation.rst | 50 +++ .../doc/source/intro.rst | 23 ++ .../doc/source/manual.rst | 45 +++ .../doc/source/percona-pam-plugin-logo.png | Bin 0 -> 12447 bytes .../doc/source/percona_favicon.ico | Bin 0 -> 894 bytes .../doc/source/release-notes.rst | 19 + .../percona-pam-for-mysql/src/auth_mapping.cc | 227 ++++++++++++ .../percona-pam-for-mysql/src/auth_mapping.h | 73 ++++ plugin/percona-pam-for-mysql/src/auth_pam.cc | 179 ++++++++++ .../src/auth_pam_common.cc | 208 +++++++++++ .../src/auth_pam_common.h | 82 +++++ .../src/auth_pam_compat.cc | 129 +++++++ plugin/percona-pam-for-mysql/src/dialog.cc | 327 ++++++++++++++++++ plugin/percona-pam-for-mysql/src/groups.cc | 146 ++++++++ plugin/percona-pam-for-mysql/src/groups.h | 45 +++ .../src/lib_auth_pam_client.c | 72 ++++ .../src/lib_auth_pam_client.h | 77 +++++ .../src/test_auth_pam_client.c | 69 ++++ .../percona_pam/pam_mapping_test.py | 118 +++++++ plugin/semisync/semisync_replica.cc | 2 +- 44 files changed, 2702 insertions(+), 12 deletions(-) create mode 100644 mysql-test/r/percona_udf.result create mode 100644 mysql-test/t/percona_udf.test create mode 100644 plugin/keyring/common/secure_string.h create mode 100644 plugin/keyring/keyring_file.version create mode 100644 plugin/percona-pam-for-mysql/CMakeLists.txt create mode 100644 plugin/percona-pam-for-mysql/doc/make.bat create mode 100644 plugin/percona-pam-for-mysql/doc/source/_static/percona-pam-plugin-logo.jpg create mode 100644 plugin/percona-pam-for-mysql/doc/source/_static/percona_favicon.ico create mode 100644 plugin/percona-pam-for-mysql/doc/source/conf.py create mode 100644 plugin/percona-pam-for-mysql/doc/source/faq.rst create mode 100644 plugin/percona-pam-for-mysql/doc/source/glossary.rst create mode 100644 plugin/percona-pam-for-mysql/doc/source/index.rst create mode 100644 plugin/percona-pam-for-mysql/doc/source/installation.rst create mode 100644 plugin/percona-pam-for-mysql/doc/source/intro.rst create mode 100644 plugin/percona-pam-for-mysql/doc/source/manual.rst create mode 100644 plugin/percona-pam-for-mysql/doc/source/percona-pam-plugin-logo.png create mode 100644 plugin/percona-pam-for-mysql/doc/source/percona_favicon.ico create mode 100644 plugin/percona-pam-for-mysql/doc/source/release-notes.rst create mode 100644 plugin/percona-pam-for-mysql/src/auth_mapping.cc create mode 100644 plugin/percona-pam-for-mysql/src/auth_mapping.h create mode 100644 plugin/percona-pam-for-mysql/src/auth_pam.cc create mode 100644 plugin/percona-pam-for-mysql/src/auth_pam_common.cc create mode 100644 plugin/percona-pam-for-mysql/src/auth_pam_common.h create mode 100644 plugin/percona-pam-for-mysql/src/auth_pam_compat.cc create mode 100644 plugin/percona-pam-for-mysql/src/dialog.cc create mode 100644 plugin/percona-pam-for-mysql/src/groups.cc create mode 100644 plugin/percona-pam-for-mysql/src/groups.h create mode 100644 plugin/percona-pam-for-mysql/src/lib_auth_pam_client.c create mode 100644 plugin/percona-pam-for-mysql/src/lib_auth_pam_client.h create mode 100644 plugin/percona-pam-for-mysql/src/test_auth_pam_client.c create mode 100755 plugin/percona-pam-for-mysql/test/dbqp/percona_tests/percona_pam/pam_mapping_test.py diff --git a/include/my_bitmap.h b/include/my_bitmap.h index 0654edf8cc07..b81a8d07ba81 100644 --- a/include/my_bitmap.h +++ b/include/my_bitmap.h @@ -71,7 +71,7 @@ extern uint bitmap_bits_set(const MY_BITMAP *map); extern void bitmap_free(MY_BITMAP *map); extern void bitmap_set_above(MY_BITMAP *map, uint from_byte, bool use_bit); extern void bitmap_set_prefix(MY_BITMAP *map, uint prefix_size); -extern void bitmap_intersect(MY_BITMAP *to, const MY_BITMAP *from); +extern void bitmap_intersect(MY_BITMAP *map, const MY_BITMAP *map2); extern void bitmap_subtract(MY_BITMAP *map, const MY_BITMAP *map2); extern void bitmap_union(MY_BITMAP *map, const MY_BITMAP *map2); extern void bitmap_xor(MY_BITMAP *map, const MY_BITMAP *map2); @@ -136,4 +136,4 @@ static inline void bitmap_set_all(MY_BITMAP *map) { memset(map->bitmap, 0xFF, 4 * no_words_in_map(map)); } -#endif // MY_BITMAP_INCLUDED +#endif /* MY_BITMAP_INCLUDED */ diff --git a/include/my_sys.h b/include/my_sys.h index 3f41a739fd84..38a2261f42dd 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -611,6 +611,9 @@ extern size_t my_fwrite(FILE *stream, const uchar *Buffer, size_t Count, myf MyFlags); extern my_off_t my_fseek(FILE *stream, my_off_t pos, int whence); extern my_off_t my_ftell(FILE *stream); +#if !defined(HAVE_MEMSET_S) +void memset_s(void *dest, size_t dest_max, int c, size_t n); +#endif /* implemented in my_syslog.c */ diff --git a/mysql-test/include/plugin.defs b/mysql-test/include/plugin.defs index feb8d3b95237..9463aa15723f 100644 --- a/mysql-test/include/plugin.defs +++ b/mysql-test/include/plugin.defs @@ -192,3 +192,10 @@ component_test_execute_prepared_statement plugin_output_directory no COMPONEN # component_test_execute_regular_statement component_test_execute_regular_statement plugin_output_directory no COMPONENT_TEST_EXECUTE_REGULAR_STATEMENT + +# Percona additions +auth_socket plugin_output_directory no SOCKET_AUTH +ha_rocksdb plugin_output_directory no ROCKSDB rocksdb,rocksdb_cfstats,rocksdb_dbstats,rocksdb_perf_context,rocksdb_perf_context_global,rocksdb_cf_options,rocksdb_compaction_history,rocksdb_compaction_stats,rocksdb_active_compaction_stats,rocksdb_global_info,rocksdb_ddl,rocksdb_index_file_map,rocksdb_locks,rocksdb_trx,rocksdb_deadlock,rocksdb_sst_props,rocksdb_live_files_metadata +auth_pam plugin_output_directory no AUTH_PAM +auth_pam_compat plugin_output_directory no AUTH_PAM_COMPAT +keyring_vault plugin_output_directory no KEYRING_VAULT_PLUGIN keyring_vault diff --git a/mysql-test/r/percona_udf.result b/mysql-test/r/percona_udf.result new file mode 100644 index 000000000000..c7d9bb1de710 --- /dev/null +++ b/mysql-test/r/percona_udf.result @@ -0,0 +1,37 @@ +*** checking if UDF works without creating the function +SELECT MURMUR_HASH('hello', 'world'); +ERROR 42000: FUNCTION test.MURMUR_HASH does not exist + +*** creating UDF functions +CREATE FUNCTION fnv1a_64 RETURNS INTEGER SONAME 'libfnv1a_udf.so'; +CREATE FUNCTION fnv_64 RETURNS INTEGER SONAME 'libfnv_udf.so'; +CREATE FUNCTION murmur_hash RETURNS INTEGER SONAME 'libmurmur_udf.so'; + +*** checking UDF functions +include/assert.inc [checking hash value for FNV1A_64()] +include/assert.inc [checking hash value for FNV_64()] +include/assert.inc [checking hash value for MURMUR_HASH()] + +*** creating t1 table +CREATE TABLE t1 ( +id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, +col1 INT, +col2 VARCHAR(16), +col3 TEXT, +PRIMARY KEY(id) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +INSERT INTO t1(col1, col2, col3) VALUES (1, "b", "c"); +INSERT INTO t1(col1, col2, col3) VALUES (2, "3240", "4000"); +INSERT INTO t1(col1, col2, col3) VALUES (3, "short", "words"); + +*** checking UDF functions with t1 table +include/assert.inc [checking hash value for BIT_XOR(FNV1A_64())] +include/assert.inc [checking hash value for BIT_XOR(FNV_64())] +include/assert.inc [checking hash value for BIT_XOR(MURMUR_HASH())] +include/assert.inc [checking hash value for FNV1A_64(col1, col2, col3)] + +*** cleaning up +DROP FUNCTION fnv1a_64; +DROP FUNCTION fnv_64; +DROP FUNCTION murmur_hash; +DROP TABLE t1; diff --git a/mysql-test/t/percona_udf.test b/mysql-test/t/percona_udf.test new file mode 100644 index 000000000000..d7504e2ef6da --- /dev/null +++ b/mysql-test/t/percona_udf.test @@ -0,0 +1,65 @@ +--echo *** checking if UDF works without creating the function +--error ER_SP_DOES_NOT_EXIST +SELECT MURMUR_HASH('hello', 'world'); + +--echo +--echo *** creating UDF functions +CREATE FUNCTION fnv1a_64 RETURNS INTEGER SONAME 'libfnv1a_udf.so'; +CREATE FUNCTION fnv_64 RETURNS INTEGER SONAME 'libfnv_udf.so'; +CREATE FUNCTION murmur_hash RETURNS INTEGER SONAME 'libmurmur_udf.so'; + +--echo +--echo *** checking UDF functions + +--let $assert_text = checking hash value for FNV1A_64() +--let $assert_cond = `SELECT FNV1A_64('hello', 'world') = 1214055856804091265` +--source include/assert.inc + +--let $assert_text = checking hash value for FNV_64() +--let $assert_cond = `SELECT FNV_64('hello', 'world') = 2924375256774005480` +--source include/assert.inc + +--let $assert_text = checking hash value for MURMUR_HASH() +--let $assert_cond = `SELECT MURMUR_HASH('hello', 'world') = -7857203399167365490` +--source include/assert.inc + +--echo +--echo *** creating t1 table +CREATE TABLE t1 ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + col1 INT, + col2 VARCHAR(16), + col3 TEXT, + PRIMARY KEY(id) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +INSERT INTO t1(col1, col2, col3) VALUES (1, "b", "c"); +INSERT INTO t1(col1, col2, col3) VALUES (2, "3240", "4000"); +INSERT INTO t1(col1, col2, col3) VALUES (3, "short", "words"); + +--echo +--echo *** checking UDF functions with t1 table + +--let $assert_text = checking hash value for BIT_XOR(FNV1A_64()) +--let $assert_cond = [SELECT BIT_XOR(CAST(FNV1A_64(col1, col2, col3) AS UNSIGNED)) FROM t1] = 9092447622868433808 +--source include/assert.inc + +--let $assert_text = checking hash value for BIT_XOR(FNV_64()) +--let $assert_cond = [SELECT BIT_XOR(CAST(FNV_64(col1, col2, col3) AS UNSIGNED)) FROM t1] = 3813096360487945067 +--source include/assert.inc + +--let $assert_text = checking hash value for BIT_XOR(MURMUR_HASH()) +--let $assert_cond = [SELECT BIT_XOR(CAST(MURMUR_HASH(col1, col2, col3) AS UNSIGNED)) FROM t1] = 9393970666296144275 +--source include/assert.inc + +--let $assert_text = checking hash value for FNV1A_64(col1, col2, col3) +--let $assert_cond = ["SELECT FNV1A_64(col1, col2, col3) AS Hash_Val FROM t1", Hash_Val, 1] = 5882002141665055715 +--source include/assert.inc + +--echo +--echo *** cleaning up +DROP FUNCTION fnv1a_64; +DROP FUNCTION fnv_64; +DROP FUNCTION murmur_hash; + +DROP TABLE t1; diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 212600450502..8f5457406576 100644 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -27,6 +27,8 @@ SET(MY_TIME_SOURCES my_time.cc my_systime.cc) ADD_CONVENIENCE_LIBRARY(mytime ${MY_TIME_SOURCES}) +INCLUDE_DIRECTORIES(SYSTEM ${BOOST_PATCHES_DIR} ${BOOST_INCLUDE_DIR}) + SET(MYSYS_SOURCES array.cc charset.cc diff --git a/mysys/my_malloc.cc b/mysys/my_malloc.cc index 3d838fe762ac..3a914691e360 100644 --- a/mysys/my_malloc.cc +++ b/mysys/my_malloc.cc @@ -562,3 +562,16 @@ char *my_strndup(PSI_memory_key key, const char *from, size_t length, } return ptr; } + +#if !defined(HAVE_MEMSET_S) +void memset_s(void *dest, size_t dest_max, int c, size_t n) { +#if defined(WIN32) + SecureZeroMemory(dest, n); +#else + volatile unsigned char *p = static_cast(dest); + while (dest_max-- && n--) { + *p++ = c; + } +#endif +} +#endif diff --git a/plugin/daemon_example/daemon_example.cc b/plugin/daemon_example/daemon_example.cc index 423297607cad..7492c3c8529c 100644 --- a/plugin/daemon_example/daemon_example.cc +++ b/plugin/daemon_example/daemon_example.cc @@ -28,6 +28,7 @@ #include #include #include +#include #include "m_string.h" // strlen #include "my_dbug.h" @@ -63,6 +64,7 @@ static void init_deamon_example_psi_keys() { struct mysql_heartbeat_context { my_thread_handle heartbeat_thread; File heartbeat_file; + std::atomic_bool done; }; static void *mysql_heartbeat(void *p) { @@ -72,7 +74,7 @@ static void *mysql_heartbeat(void *p) { time_t result; struct tm tm_tmp; - while (true) { + while (!con->done.load()) { sleep(5); result = time(nullptr); @@ -125,6 +127,7 @@ static int daemon_example_plugin_init(void *p) { MY_REPLACE_EXT | MY_UNPACK_FILENAME); unlink(heartbeat_filename); con->heartbeat_file = my_open(heartbeat_filename, O_CREAT | O_RDWR, MYF(0)); + con->done.store(false); /* No threads exist at this point in time, so this is thread safe. @@ -173,7 +176,7 @@ static int daemon_example_plugin_deinit(void *p) { struct tm tm_tmp; void *dummy_retval; - my_thread_cancel(&con->heartbeat_thread); + con->done.store(true); localtime_r(&result, &tm_tmp); snprintf(buffer, sizeof(buffer), diff --git a/plugin/group_replication/libmysqlgcs/src/bindings/xcom/xcom/network/xcom_network_provider_ssl_native_lib.cc b/plugin/group_replication/libmysqlgcs/src/bindings/xcom/xcom/network/xcom_network_provider_ssl_native_lib.cc index 92bf97a8e948..6daf24869cb7 100644 --- a/plugin/group_replication/libmysqlgcs/src/bindings/xcom/xcom/network/xcom_network_provider_ssl_native_lib.cc +++ b/plugin/group_replication/libmysqlgcs/src/bindings/xcom/xcom/network/xcom_network_provider_ssl_native_lib.cc @@ -52,6 +52,11 @@ #include "openssl/engine.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#endif + #include "xcom/retry.h" #include "xcom/task_debug.h" #include "xcom/x_platform.h" diff --git a/plugin/keyring/common/i_keyring_io.h b/plugin/keyring/common/i_keyring_io.h index 8610c7e1e526..90e73cb479d4 100644 --- a/plugin/keyring/common/i_keyring_io.h +++ b/plugin/keyring/common/i_keyring_io.h @@ -31,7 +31,7 @@ namespace keyring { class IKeyring_io : public Keyring_alloc { public: - virtual bool init(std::string *keyring_storage_url) = 0; + virtual bool init(const std::string *keyring_storage_url) = 0; virtual bool flush_to_backup(ISerialized_object *serialized_object) = 0; virtual bool flush_to_storage(ISerialized_object *serialized_object) = 0; diff --git a/plugin/keyring/common/i_keyring_key.h b/plugin/keyring/common/i_keyring_key.h index f1bb924129b8..34f5bb70e89f 100644 --- a/plugin/keyring/common/i_keyring_key.h +++ b/plugin/keyring/common/i_keyring_key.h @@ -50,6 +50,7 @@ struct IKey : public Keyring_alloc { virtual size_t get_key_data_size() = 0; virtual size_t get_key_pod_size() const = 0; virtual uchar *release_key_data() = 0; + virtual void xor_data(uchar *data, size_t data_len) = 0; virtual void xor_data() = 0; virtual void set_key_data(uchar *key_data, size_t key_data_size) = 0; virtual void set_key_type(const std::string *key_type) = 0; diff --git a/plugin/keyring/common/keyring_key.cc b/plugin/keyring/common/keyring_key.cc index 73e31db3c118..c6a992b2647d 100644 --- a/plugin/keyring/common/keyring_key.cc +++ b/plugin/keyring/common/keyring_key.cc @@ -190,12 +190,16 @@ size_t Key::get_key_pod_size() const { return key_pod_size_aligned; } -void Key::xor_data() { - if (key == nullptr) return; +void Key::xor_data(uchar *data, size_t data_len) { static const char *obfuscate_str = "*305=Ljt0*!@$Hnm(*-9-w;:"; - for (size_t i = 0, l = 0; i < key_len; + for (size_t i = 0, l = 0; i < data_len; ++i, l = ((l + 1) % strlen(obfuscate_str))) - key.get()[i] ^= obfuscate_str[l]; + data[i] ^= obfuscate_str[l]; +} + +void Key::xor_data() { + if (key == nullptr) return; + xor_data(key.get(), key_len); } bool Key::is_key_id_valid() { return key_id.length() > 0; } diff --git a/plugin/keyring/common/keyring_key.h b/plugin/keyring/common/keyring_key.h index 2b1c80cadaf6..ca0cffeded8d 100644 --- a/plugin/keyring/common/keyring_key.h +++ b/plugin/keyring/common/keyring_key.h @@ -53,6 +53,7 @@ struct Key : IKey { size_t get_key_data_size() override; size_t get_key_pod_size() const override; uchar *release_key_data() override; + void xor_data(uchar *data, size_t data_len) override; void xor_data() override; void set_key_data(uchar *key_data, size_t key_data_size) override; void set_key_type(const std::string *key_type) override; @@ -69,7 +70,7 @@ struct Key : IKey { const void *a_key, size_t a_key_len); void clear_key_data(); - void create_key_signature() const; + virtual void create_key_signature() const; bool load_string_from_buffer(const uchar *buffer, size_t *buffer_position, size_t key_pod_size, std::string *string, size_t string_length); diff --git a/plugin/keyring/common/keyring_memory.h b/plugin/keyring/common/keyring_memory.h index 321bd7a65f01..6af08ae2e062 100644 --- a/plugin/keyring/common/keyring_memory.h +++ b/plugin/keyring/common/keyring_memory.h @@ -24,10 +24,13 @@ #ifndef MYSQL_KEYRING_MEMORY_H #define MYSQL_KEYRING_MEMORY_H -#include +#include #include #include +#include "my_sys.h" +#include "mysql/service_mysql_alloc.h" + namespace keyring { extern PSI_memory_key key_memory_KEYRING; @@ -49,6 +52,54 @@ class Keyring_alloc { static void operator delete(void *ptr, std::size_t) { my_free(ptr); } static void operator delete[](void *ptr, std::size_t) { my_free(ptr); } }; + +template +class Secure_allocator { + public: + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = T *; + using const_pointer = const T *; + using reference = T &; + using const_reference = const T &; + using value_type = T; + + Secure_allocator() noexcept {} + + template + Secure_allocator(const Secure_allocator &) noexcept {} + + T *allocate(size_t n) { + if (n == 0) + return nullptr; + else if (n > INT_MAX) + throw std::bad_alloc(); + return keyring_malloc(n * sizeof(T)); + } + + void deallocate(T *p, size_t n) noexcept { + memset_s(p, n, 0, n); + my_free(p); + } + + template + struct rebind { + typedef Secure_allocator other; + }; +}; + +template +bool operator==(const Secure_allocator &, + const Secure_allocator &) noexcept { + return true; +} + +template +bool operator!=(const Secure_allocator &, + const Secure_allocator &) noexcept { + return false; +} + } // namespace keyring #endif // MYSQL_KEYRING_MEMORY_H diff --git a/plugin/keyring/common/secure_string.h b/plugin/keyring/common/secure_string.h new file mode 100644 index 000000000000..35fa7f6d366c --- /dev/null +++ b/plugin/keyring/common/secure_string.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2018 Percona LLC and/or its affiliates. All rights reserved. + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef MYSQL_KEYRING_SECURE_STRING +#define MYSQL_KEYRING_SECURE_STRING + +#include +#include "keyring_memory.h" + +namespace keyring { +typedef std::basic_string, Secure_allocator> + Secure_string; +typedef std::basic_ostringstream, + Secure_allocator> + Secure_ostringstream; +typedef std::basic_istringstream, + Secure_allocator> + Secure_istringstream; +} // namespace keyring + +#endif // MYSQL_KEYRING_SECURE_STRING diff --git a/plugin/keyring/keyring_file.version b/plugin/keyring/keyring_file.version new file mode 100644 index 000000000000..023b6b555b21 --- /dev/null +++ b/plugin/keyring/keyring_file.version @@ -0,0 +1,8 @@ +KEYRING_FILE_VERSION_1.0 { + global: + _mysql_*; + mysql_malloc_service; + my_plugin_log_service; + security_context_service; + local: *; +}; diff --git a/plugin/percona-pam-for-mysql/CMakeLists.txt b/plugin/percona-pam-for-mysql/CMakeLists.txt new file mode 100644 index 000000000000..7c7bb8de2808 --- /dev/null +++ b/plugin/percona-pam-for-mysql/CMakeLists.txt @@ -0,0 +1,42 @@ +# (C) 2011-2013 Percona LLC and/or its affiliates +# +# 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 02110-1301, USA +# +IF(WITH_PAM) +INCLUDE (CheckLibraryExists) +CHECK_LIBRARY_EXISTS(pam pam_authenticate "" HAVE_PAM) +IF(NOT HAVE_PAM) + MESSAGE(FATAL_ERROR "Required PAM dev library not found. Please install PAM development files!") +ENDIF(NOT HAVE_PAM) +CHECK_SYMBOL_EXISTS(getpwnam_r "pwd.h" HAVE_GETPWNAM_R) +CHECK_SYMBOL_EXISTS(getgrgid_r "grp.h" HAVE_GETGRGID_R) +CHECK_INCLUDE_FILES (security/pam_misc.h HAVE_SECURITY_PAM_MISC_H) +CHECK_INCLUDE_FILES (security/openpam.h HAVE_SECURITY_OPENPAM_H) +CHECK_INCLUDE_FILES (dlfcn.h HAVE_DLFCN_H) +ADD_DEFINITIONS(-Dget_tty_password=dialog_mysql_get_tty_password) +IF(HAVE_PAM AND HAVE_GETPWNAM_R AND HAVE_GETGRGID_R AND HAVE_DLFCN_H) + SET(AUTH_PAM_COMMON_SOURCES + src/auth_pam_common.cc src/lib_auth_pam_client.c src/lib_auth_pam_client.h + src/auth_mapping.h src/auth_mapping.cc src/groups.cc src/groups.h) + SET(AUTH_PAM_SOURCES ${AUTH_PAM_COMMON_SOURCES} src/auth_pam.cc) + SET(AUTH_PAM_COMPAT_SOURCES ${AUTH_PAM_COMMON_SOURCES} src/auth_pam_compat.cc) + MYSQL_ADD_PLUGIN(auth_pam ${AUTH_PAM_SOURCES} LINK_LIBRARIES pam MODULE_ONLY) + MYSQL_ADD_PLUGIN(auth_pam_compat ${AUTH_PAM_COMPAT_SOURCES} LINK_LIBRARIES pam MODULE_ONLY) + MYSQL_ADD_PLUGIN(dialog + src/dialog.cc + ../../sql-common/get_password.cc + LINK_LIBRARIES perconaserverclient + MODULE_ONLY) +ENDIF(HAVE_PAM AND HAVE_GETPWNAM_R AND HAVE_GETGRGID_R AND HAVE_DLFCN_H) +ENDIF(WITH_PAM) diff --git a/plugin/percona-pam-for-mysql/doc/make.bat b/plugin/percona-pam-for-mysql/doc/make.bat new file mode 100644 index 000000000000..4bc6b71e63ac --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/make.bat @@ -0,0 +1,170 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PerconaXtraBackup.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PerconaXtraBackup.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/plugin/percona-pam-for-mysql/doc/source/_static/percona-pam-plugin-logo.jpg b/plugin/percona-pam-for-mysql/doc/source/_static/percona-pam-plugin-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c9f9d668bba9eb313a6c84c0d944073c9067bb3 GIT binary patch literal 36163 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 literal 0 HcmV?d00001 diff --git a/plugin/percona-pam-for-mysql/doc/source/_static/percona_favicon.ico b/plugin/percona-pam-for-mysql/doc/source/_static/percona_favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f426064d6f505e77365a8980046b58ae465ea6b0 GIT binary patch literal 894 zcma)*y-QnR6o=2f1!I;Hp`@aNT`C2!h#-ZEi~0xX;8Hu38qlQ*ox_(f?^{iiV^YKL1IHplY4#M`^rTkf;YeS@;mQ&&USYFXmp^NlI@J9BQIe;k z(7CLs*^{Tm%esmOqlG%$X{~wSukq}gkcBW*F#!N|rgw=JOye#;bCk`Wvs@9{nXJn& zG3KUg_4yxH{sTW-un~rkj_S`kEr5%v){K&{_`^(+RM%$4oWA8TgglZOvOf$4T=hO? zRIH$;{_SdMJ{{5as}k=y@QX z_io42yoUznE;!H`sf8ibDehqUl@0$$7;OjM_CtiJ=s)`~et|G|_rHV^?kn#8c4_vU zdHc?_@HruX3u`m6bzL@kl|PazHc92cZb*umznxDZ%zZLdBC_WZ3Gv v documentation". +html_title = 'Percona PAM authenticatino plugin for MySQL Documentation' + +# A shorter title for the navigation bar. Default is the same as html_title. +html_short_title = 'PAM Plugin Docs' + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = 'percona-pam-plugin-logo.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = 'percona_favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PerconaPAMForMySQL' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'PerconaPAMForMySQL.tex', u'Percona PAM Authentication Plugin for MySQL Documentation', + u'Percona Inc', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ +# ('index', 'perconapamplugin', u'Percona PAM Authentication Plugin for MySQL Documentation', +# [u'Percona Inc'], 1) +] diff --git a/plugin/percona-pam-for-mysql/doc/source/faq.rst b/plugin/percona-pam-for-mysql/doc/source/faq.rst new file mode 100644 index 000000000000..0188c77d35a2 --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/source/faq.rst @@ -0,0 +1,51 @@ +============================ + Frequently Asked Questions +============================ + +Is there a Windows version? +=========================== + +No, Windows does not support PAM, so there will not be a Windows version. + +Can I use it with MySQL? +======================== + +Yes. + +Can I use it with Percona Server? +================================= + +Yes. + + +Is it Free and Open Source Software? +==================================== + +Yes. + + +Can I use the PAM plugin to authenticate against /etc/shadow? +============================================================= + +Yes, you need to add the mysql user to the shadow group. Because PAM libraries, such as 'pam_unix.so', need to access /etc/shadow. + +For example this is how you can do it in *Ubuntu*: :: + + root@lucid64:/var/lib/mysql# getent group shadow + shadow:x:42:mysql + + root@lucid64:/var/lib/mysql# ls -alhs /etc/shadow + 4.0K -rw-r----- 1 root shadow 912 Dec 21 10:39 /etc/shadow + +After you restart mysqld for changes to take effect, pam_unix authentication will work. + +The other option is to run mysqld as root. This should be used for testing only or as a last resort method. + + +I'm getting the: "ERROR 2059 (HY000): Authentication plugin 'auth_pam' cannot be loaded" +======================================================================================== + +This means that the default client :option:`plugin-dir` setting doesn't work or it isn't set up properly. You'll need to add the location of the plugin folder to your client configuration: :: + + [client] + plugin_dir='/usr/lib/mysql/plugin' diff --git a/plugin/percona-pam-for-mysql/doc/source/glossary.rst b/plugin/percona-pam-for-mysql/doc/source/glossary.rst new file mode 100644 index 000000000000..70834a733210 --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/source/glossary.rst @@ -0,0 +1,8 @@ +========== + Glossary +========== + +.. glossary:: + + PAM + Pluggable Authentication Module - allows integrating multiple authentication mechanisms which can be written independently of the underlying authentication scheme that's being used in the application. diff --git a/plugin/percona-pam-for-mysql/doc/source/index.rst b/plugin/percona-pam-for-mysql/doc/source/index.rst new file mode 100644 index 000000000000..b249b9fceff4 --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/source/index.rst @@ -0,0 +1,51 @@ +============================================================ + Percona PAM authentication plugin For MySQL - Documentation +============================================================ + +Percona PAM authentication plugin for MySQL. + +Introduction +============ + +.. toctree:: + :maxdepth: 1 + :glob: + + intro + +Installation +============ + +.. toctree:: + :maxdepth: 2 + :glob: + + installation + +User's Manual +============= + +.. toctree:: + :maxdepth: 2 + :glob: + + manual + +Miscellaneous +============= + +.. toctree:: + :maxdepth: 1 + :glob: + + faq + release-notes + glossary + +Indices and tables +================== + +* :ref:`genindex` + +* :ref:`search` + diff --git a/plugin/percona-pam-for-mysql/doc/source/installation.rst b/plugin/percona-pam-for-mysql/doc/source/installation.rst new file mode 100644 index 000000000000..30aec3add935 --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/source/installation.rst @@ -0,0 +1,50 @@ +======================================================= + Installing Percona PAM Authentication Plugin for MySQL +======================================================= + +.. toctree:: + :hidden: + +Compiling from Source +===================== + +You will need both the PAM headers and the MySQL 5.5 headers and corresponding `mysql_config` binary available on your system. + +If you are not using one of the pre-built binary packages, you will need to compile the plugin from source. You can either use a source tarball or the source repository. + +For getting a copy of the latest development bzr tree: :: + + $ bzr branch lp:percona-pam-for-mysql + +If you are building from bzr, you will need to generate the configure script: :: + + $ ./bootstrap + +You do not need to run `bootstrap` if you are using a source tarball. + +You then need to build the plugin: :: + + $ ./configure + $ make + +To install, you can simply run (as root or using sudo or similar): :: + + $ make install + +Installing server-side plugin +============================= + +The shared library that holds the plugin, auth_pam.so, needs to be stored in the plugindir directory of mysql. You can get this value via the command: :: + + $ mysql_config --plugindir + +Make sure that after installed, the library has got the appropiate permissions (file execution is required). + +Most packages should do this for you, so this is likely only required with the binary tarballs. + +In order to load the plugin into the working server, issue the following command: :: + + mysql> INSTALL PLUGIN auth_pam SONAME 'auth_pam.so'; + + +You can now create a PAM configuration for the MySQL server and create users that are authenticated by PAM. diff --git a/plugin/percona-pam-for-mysql/doc/source/intro.rst b/plugin/percona-pam-for-mysql/doc/source/intro.rst new file mode 100644 index 000000000000..600e2f57a6a8 --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/source/intro.rst @@ -0,0 +1,23 @@ +================================================== + About Percona PAM Authentication Plugin for MySQL +================================================== + +Percona PAM Authentication Plugin is a free and Open Source implementation of the MySQL's authentication plugin. This plugin acts as a mediator between the MySQL server, the MySQL client, and the PAM stack. The server plugin requests authentication from the PAM stack, forwards any requests and messages from the PAM stack over the wire to the client (in cleartext) and reads back any replies for the PAM stack. + + PAM plugin uses dialog as its client side plugin. Dialog plugin can be loaded to any client application that uses libmysqlclient library (or compatible client library such as libperconaserverclient). + +Here are some of the benefits that Percona dialog plugin offers over the default one: + + * It correctly recognizes whether PAM wants input to be echoed or not, while the default one always echoes the input on the user's console. + * It can use the password which is passed to |MySQL| client via "-p" parameter. + * Dialog client `installation bug `_ has been fixed. + * This plugin works on |MySQL| and |Percona Server|. + +Percona offers two versions of this plugin: + + * Full PAM plugin called *auth_pam*. This plugin uses *dialog.so*. It fully supports the PAM protocol with arbitrary communication between client and server. + * Oracle-compatible PAM called *auth_pam_compat*. This plugin uses *mysql_clear_password* which is a part of Oracle MySQL client. It also has some limitations, such as, it supports only one password input. You must use "-p" option in order to pass the password to auth_pam_compat. + +These two versions of plugins are physically different. To choose which one you want used, you must use *IDENTIFIED WITH 'auth_pam'* for auth_pam, and *IDENTIFIED WITH 'auth_pam_compat'* for auth_pam_compat. + + diff --git a/plugin/percona-pam-for-mysql/doc/source/manual.rst b/plugin/percona-pam-for-mysql/doc/source/manual.rst new file mode 100644 index 000000000000..f96e0883f2d6 --- /dev/null +++ b/plugin/percona-pam-for-mysql/doc/source/manual.rst @@ -0,0 +1,45 @@ +.. _user-manual: + +========================================================== + *Percona PAM authentication plugin for MySQL* User Manual +========================================================== + +.. toctree:: + :maxdepth: 1 + :hidden: + +Configuring PAM for MySQL +========================= + +You will need to configure PAM on your system for how it should authenticate for MySQL. A simple setup can be to use the standard UNIX authentication method. + +*NOTE:* Using pam_unix means the MySQL Server needs to read the `/etc/shadow` file, which usually means it has to be run as `root` - usually not a recommended configuration. + +A sample `/etc/pam.d/mysqld` file: :: + + auth required pam_unix.so + account required pam_unix.so + +For added information in the system log, you can expand it to be: :: + + auth required pam_warn.so + auth required pam_unix.so audit + account required pam_unix.so audit + + +Creating A User +=============== + +You will need to execute `CREATE USER` with specifying the PAM plugin. For example: :: + + CREATE USER 'username'@'host' IDENTIFIED WITH auth_pam; + +This creates a user `username` that can connect from `host` and will be authenticated using the PAM plugin. If you are using the `pam_unix` method in PAM (or similar) you will need to have an account for `username` existing on the system. + +Supplementary groups support +============================ + +|Percona Server| has implemented PAM plugin support for supplementary groups. Supplementary or secondary groups are extra groups a specific user is member of. For example user ``joe`` might be a member of groups: ``joe`` (his primary group) and secondary groups ``developers`` and ``dba``. A complete list of groups and users belonging to them can be checked with ``cat /etc/group`` command. + +This feature enables using secondary groups in the mapping part of the authentication string, like "``mysql, developers=joe, dba=mark``". Previously only primary groups could have been specified there. If user is a member of both ``developers`` and ``dba``, PAM plugin will map it to the ``joe`` because ``developers`` matches first. + diff --git a/plugin/percona-pam-for-mysql/doc/source/percona-pam-plugin-logo.png b/plugin/percona-pam-for-mysql/doc/source/percona-pam-plugin-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..32afd1cab4faa1ff52ee702ce965af2b95ba4c28 GIT binary patch literal 12447 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 on June 1st, 2012. With this release PAM Authentication plugin became GA. Further information on how to install and use the plugin can be found in the |Percona Server| documentation. + +Preview Release +=============== + +Percona is pleased to announce availability of an early access version of Percona's PAM Authentication plugin for MySQL on December 5th, 2011. This plugin supports |MySQL|-5.5.x, |Percona Server| 5.5.x and MariaDB 5.2.x. The PAM Authentication plugin can be used for: + * |MySQL| authentication using operating system users (``pam_unix``) + * |MySQL| authentication from LDAP server (pam_ldap) + * authentication against RSA SecurID server + * any other authentication methods that provides access via PAM + +Percona PAM Authentication Plugin for MySQL is fully open source, free of charge and can be used on an unlimited amount of servers. diff --git a/plugin/percona-pam-for-mysql/src/auth_mapping.cc b/plugin/percona-pam-for-mysql/src/auth_mapping.cc new file mode 100644 index 000000000000..b5215674bad7 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/auth_mapping.cc @@ -0,0 +1,227 @@ +/* +(C) 2012, 2016 Percona LLC and/or its affiliates + +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 02110-1301, USA +*/ + +#include "auth_mapping.h" +#include +#include +#include +#include "groups.h" + +#include "auth_pam_common.h" + +/** Token representation: + token type, string repr, length of token */ +struct token { + enum class ctype { id, comma, eq, eof } type; + const char *token; + size_t token_len; +}; + +/** Iterator in key-value mapping: + position and length of key, + position and length of value, + current position in string */ +struct mapping_iter { + const char *key; + size_t key_len; + const char *value; + size_t value_len; + const char *ptr; +}; + +/** Get next token from buf. Returns new buf position. */ +static const char *get_token(struct token *token, const char *buf) { + const char *ptr = buf; + + while (*ptr && isspace(*ptr)) ++ptr; + + token->token = ptr; + switch (*ptr) { + case '\0': + token->type = token::ctype::eof; + break; + case ',': + token->token_len = 1; + token->type = token::ctype::comma; + ++ptr; + break; + case '=': + token->token_len = 1; + token->type = token::ctype::eq; + ++ptr; + break; + case '"': + token->token_len = 0; + ++ptr; + token->token = ptr; + while (*ptr && *ptr != '"') { + ++token->token_len; + ++ptr; + } + token->type = token::ctype::id; + if (*ptr) ++ptr; + break; + default: + token->token_len = 0; + while (*ptr && !isspace(*ptr) && *ptr != ',' && *ptr != '=') { + ++token->token_len; + ++ptr; + } + token->type = token::ctype::id; + } + + return ptr; +} + +/** Create iterator through mapping string. + Initially iterator set to position before first + key-value pair. On success non-NULL pointer returned, otherwise NULL */ +struct mapping_iter *mapping_iter_new(const char *mapping_string) { + struct mapping_iter *it = static_cast( + my_malloc(key_memory_pam_mapping_iter, sizeof(struct mapping_iter), 0)); + if (it != nullptr) { + it->key = nullptr; + it->value = nullptr; + /* eat up service name and move to (, key = value)* part */ + struct token token; + it->ptr = get_token(&token, mapping_string); + } + return it; +} + +/** Move iterator to next key-value pair. + On success pointer to key position in string returned, + otherwise NULL */ +const char *mapping_iter_next(struct mapping_iter *it) { + struct token token[4] = {{token::ctype::id, 0, 0}}; + + /* read next 4 tokens */ + it->ptr = get_token( + token + 3, + get_token(token + 2, get_token(token + 1, get_token(token, it->ptr)))); + + /* was it ", id = id"? */ + if (!((token[0].type == token::ctype::comma) && + (token[1].type == token::ctype::id) && + (token[2].type == token::ctype::eq) && + (token[3].type == token::ctype::id))) { + /* we got something inconsistent */ + return nullptr; + } + + /* set key */ + it->key = token[1].token; + it->key_len = token[1].token_len; + + /* set value */ + it->value = token[3].token; + it->value_len = token[3].token_len; + + return it->key; +} + +/** Finish iteration and release iterator */ +void mapping_iter_free(struct mapping_iter *it) { my_free(it); } + +/** Get mapped value for given user name. + Value is looked up by using all user groups as a key. + Auth string is iterated only once, while groups are iterated + for every key-value pair. This is mean than auth string order + is dominant. + + Example: + + given: + user "foo" is the member of "wheel", "staff" and "bar". + auth string is "mysql, root=user1, bar=user2, staff=user3" + + result is "user2". + + On success value_buf returned, otherwise NULL */ +char *mapping_lookup_user(const char *user_name, char *value_buf, + size_t value_buf_len, const char *mapping_string) { + /* Iterate through the key-value list stored in auth_string and + find key (which is interpreted as group name) in the list of groups + for specified user. If match is found, store appropriate value in + the authenticated_as field. */ + struct mapping_iter *keyval_it = mapping_iter_new(mapping_string); + if (keyval_it == nullptr) return nullptr; + + struct groups_iter *group_it = groups_iter_new(user_name); + if (group_it == nullptr) { + mapping_iter_free(keyval_it); + return nullptr; + } + + const char *key; + const char *group; + while ((key = mapping_iter_next(keyval_it)) != nullptr) { + while ((group = groups_iter_next(group_it)) != nullptr) { + if (keyval_it->key_len == strlen(group) && + strncmp(key, group, keyval_it->key_len) == 0) { + /* match is found */ + memcpy(value_buf, keyval_it->value, + std::min(value_buf_len, keyval_it->value_len)); + value_buf[std::min(value_buf_len, keyval_it->value_len)] = '\0'; + groups_iter_free(group_it); + mapping_iter_free(keyval_it); + return value_buf; + } + } + groups_iter_reset(group_it); + } + + groups_iter_free(group_it); + mapping_iter_free(keyval_it); + + return nullptr; +} + +/** Get key in current iterator pos. On success buf returned, + otherwise NULL */ +char *mapping_iter_get_key(struct mapping_iter *it, char *buf, size_t buf_len) { + if (it->key == nullptr) return nullptr; + memcpy(buf, it->key, std::min(buf_len, it->key_len)); + buf[std::min(buf_len, it->key_len)] = '\0'; + return buf; +} + +/** Get value in current iterator pos. On success buf returned, + otherwise NULL */ +char *mapping_iter_get_value(struct mapping_iter *it, char *buf, + size_t buf_len) { + if (it->value == nullptr) return nullptr; + memcpy(buf, it->value, std::min(buf_len, it->value_len)); + buf[std::min(buf_len, it->value_len)] = '\0'; + return buf; +} + +/** Get value by key. On success pointer to service_name + returned, otherwise NULL */ +char *mapping_get_service_name(char *buf, size_t buf_len, + const char *mapping_string) { + struct token token; + + get_token(&token, mapping_string); + if (token.type == token::ctype::id) { + memcpy(buf, token.token, std::min(buf_len, token.token_len)); + buf[std::min(buf_len, token.token_len)] = '\0'; + return buf; + } + + return nullptr; +} diff --git a/plugin/percona-pam-for-mysql/src/auth_mapping.h b/plugin/percona-pam-for-mysql/src/auth_mapping.h new file mode 100644 index 000000000000..7a98e30bcb0a --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/auth_mapping.h @@ -0,0 +1,73 @@ +#ifndef AUTH_MAPPING_INCLUDED +#define AUTH_MAPPING_INCLUDED +/* + (C) 2012, 2013 Percona LLC and/or its affiliates + + 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 02110-1301, USA + +*/ + +/** + @file + + PAM authentication for MySQL, interface for user mapping. + +*/ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** Mapping iterator. It's not exposed outsude */ +struct mapping_iter; + +/** Create iterator through mapping string. + Initially iterator set to position before first + key-value pair. On success non-NULL pointer returned, otherwise NULL */ +struct mapping_iter *mapping_iter_new(const char *mapping_string); + +/** Move iterator to next key-value pair. + On success pointer to key position in string returned, + otherwise NULL */ +const char *mapping_iter_next(struct mapping_iter *it); + +/** Finish iteration and release iterator */ +void mapping_iter_free(struct mapping_iter *it); + +/** Get key at current iterator position. On success buf returned, + otherwise NULL */ +char *mapping_iter_get_key(struct mapping_iter *it, char *buf, size_t buf_len); + +/** Get value at current iterator position. On success buf returned, + otherwise NULL */ +char *mapping_iter_get_value(struct mapping_iter *it, char *buf, + size_t buf_len); + +/** Get value by given key. On success value_buf returned, + otherwise NULL */ +char *mapping_lookup_user(const char *key, char *value_buf, + size_t value_buf_len, const char *mapping_string); + +/** Get service name for auth_string. On success buf returned, + otherwise NULL */ +char *mapping_get_service_name(char *buf, size_t buf_len, + const char *mapping_string); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugin/percona-pam-for-mysql/src/auth_pam.cc b/plugin/percona-pam-for-mysql/src/auth_pam.cc new file mode 100644 index 000000000000..5fabfe161466 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/auth_pam.cc @@ -0,0 +1,179 @@ +/* +(C) 2012, 2015 Percona LLC and/or its affiliates + +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 02110-1301, USA + +*/ + +/** + @file + + PAM authentication for MySQL, server-side plugin for the + production use. + + A general-purpose PAM authentication plugin for MySQL. Acts as a mediator + between the MySQL server, the MySQL client, and the PAM backend. Dialog plugin + used as client plugin. + + The server plugin requests authentication from the PAM backend, forwards any + requests and messages from the PAM backend over the wire to the client (in + cleartext) and reads back any replies for the backend. + + This plugin does not encrypt the communication channel in any way. If this is + required, a SSL connection should be used. + + To install this plugin, copy the .so file to the plugin directory and do + + INSTALL PLUGIN auth_pam SONAME 'auth_pam.so'; + + To use this plugin for one particular user, specify it at user's creation time + (TODO: tested with localhost only): + + CREATE USER 'username'@'hostname' IDENTIFIED WITH auth_pam; + + Alternatively UPDATE the mysql.user table to set the plugin value for an + existing user. + + Also it is possible to use this plugin to authenticate anonymous users: + + CREATE USER ''@'hostname' IDENTIFIED WITH auth_pam; + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "auth_pam_common.h" +#include "my_sys.h" +#include "mysql/psi/mysql_memory.h" + +MYSQL_PLUGIN auth_pam_plugin_info; + +/** The maximum length of buffered PAM messages, i.e. any messages up to the + next PAM reply-requiring message. 10K should be more than enough by order + of magnitude. */ +static const constexpr auto max_pam_buffered_msg_len = 10240; + +static PSI_memory_key key_memory_pam_msg_buf; + +static PSI_memory_info pam_auth_memory[] = { + {&key_memory_pam_msg_buf, "auth_pam_msg_buf", PSI_FLAG_ONLY_GLOBAL_STAT, + PSI_VOLATILITY_UNKNOWN, PSI_DOCUMENT_ME}, +}; + +struct pam_msg_buf { + unsigned char buf[max_pam_buffered_msg_len]; + unsigned char *ptr; +}; + +static char pam_msg_style_to_char(int pam_msg_style) { + /* Magic byte for the dialog plugin, '\2' is defined as ORDINARY_QUESTION + and '\4' as PASSWORD_QUESTION there. */ + return (pam_msg_style == PAM_PROMPT_ECHO_ON) ? '\2' : '\4'; +} + +int auth_pam_client_talk_init(void **talk_data) { + struct pam_msg_buf *msg_buf = static_cast(my_malloc( + key_memory_pam_msg_buf, sizeof(struct pam_msg_buf), MY_ZEROFILL)); + *talk_data = (void *)msg_buf; + if (msg_buf != nullptr) { + msg_buf->ptr = msg_buf->buf + 1; + return PAM_SUCCESS; + } + return PAM_BUF_ERR; +} + +void auth_pam_client_talk_finalize(void *talk_data) { my_free(talk_data); } + +int auth_pam_talk_perform(const struct pam_message *msg, + struct pam_response *resp, struct pam_conv_data *data, + void *talk_data) { + struct pam_msg_buf *msg_buf = (struct pam_msg_buf *)talk_data; + + /* Append the PAM message or prompt to the unsent message buffer */ + if (msg->msg) { + unsigned char *last_buf_pos = msg_buf->buf + max_pam_buffered_msg_len - 1; + if (msg_buf->ptr + strlen(msg->msg) >= last_buf_pos) { + /* Cannot happen: the PAM message buffer too small. */ + MY_ASSERT_UNREACHABLE(); + return PAM_CONV_ERR; + } + memcpy(msg_buf->ptr, msg->msg, strlen(msg->msg)); + msg_buf->ptr += strlen(msg->msg); + *(msg_buf->ptr)++ = '\n'; + } + + if (msg->msg_style == PAM_PROMPT_ECHO_OFF || + msg->msg_style == PAM_PROMPT_ECHO_ON) { + int pkt_len; + unsigned char *pkt; + + msg_buf->buf[0] = pam_msg_style_to_char(msg->msg_style); + + /* Write the message. */ + if (data->vio->write_packet(data->vio, msg_buf->buf, + msg_buf->ptr - msg_buf->buf - 1)) + return PAM_CONV_ERR; + + /* Read the answer */ + if ((pkt_len = data->vio->read_packet(data->vio, &pkt)) < 0) + return PAM_CONV_ERR; + + resp->resp = static_cast(malloc(pkt_len + 1)); + if (resp->resp == nullptr) return PAM_BUF_ERR; + + strncpy(resp->resp, (char *)pkt, pkt_len); + resp->resp[pkt_len] = '\0'; + + if (msg->msg_style == PAM_PROMPT_ECHO_OFF) + data->info->password_used = PASSWORD_USED_YES; + + msg_buf->ptr = msg_buf->buf + 1; + } + + return PAM_SUCCESS; +} + +static int auth_pam_init(MYSQL_PLUGIN plugin_info) { + int count; + auth_pam_common_init("auth_pam"); + count = array_elements(pam_auth_memory); + mysql_memory_register("auth_pam", pam_auth_memory, count); + auth_pam_plugin_info = plugin_info; + return 0; +} + +static struct st_mysql_auth pam_auth_handler = { + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "dialog", + &authenticate_user_with_pam_server, + &auth_pam_generate_auth_string_hash, + &auth_pam_validate_auth_string_hash, + &auth_pam_set_salt, + 0UL, + nullptr}; + +mysql_declare_plugin(auth_pam) { + MYSQL_AUTHENTICATION_PLUGIN, &pam_auth_handler, "auth_pam", "Percona, Inc.", + "PAM authentication plugin", PLUGIN_LICENSE_GPL, auth_pam_init, nullptr, + nullptr, 0x0001, nullptr, nullptr, nullptr +#if MYSQL_PLUGIN_INTERFACE_VERSION >= 0x103 + , + 0 +#endif +} +mysql_declare_plugin_end; diff --git a/plugin/percona-pam-for-mysql/src/auth_pam_common.cc b/plugin/percona-pam-for-mysql/src/auth_pam_common.cc new file mode 100644 index 000000000000..f819d377ae56 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/auth_pam_common.cc @@ -0,0 +1,208 @@ +/* +(C) 2011-2015 Percona LLC and/or its affiliates + +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 02110-1301, USA + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "auth_mapping.h" +#include "auth_pam_common.h" +#include "groups.h" +#include "my_sys.h" +#include "mysql/psi/mysql_memory.h" + +/* The server plugin */ + +PSI_memory_key key_memory_pam_mapping_iter; +PSI_memory_key key_memory_pam_group_iter; + +static PSI_memory_info common_pam_memory[] = { + {&key_memory_pam_mapping_iter, "auth_pam_mapping_iterator", + PSI_FLAG_ONLY_GLOBAL_STAT, PSI_VOLATILITY_UNKNOWN, PSI_DOCUMENT_ME}, + {&key_memory_pam_group_iter, "auth_pam_group_iterator", + PSI_FLAG_ONLY_GLOBAL_STAT, PSI_VOLATILITY_UNKNOWN, PSI_DOCUMENT_ME}, +}; + +/** The MySQL service name for PAM configuration */ +static const char *service_name_default = "mysqld"; + +void auth_pam_common_init(const char *psi_category) { + int count = array_elements(common_pam_memory); + mysql_memory_register(psi_category, common_pam_memory, count); +} + +static bool valid_pam_msg_style(int pam_msg_style) { + switch (pam_msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + return true; + default: + return false; + } +} + +/** The maximum length of service name. It shouldn't be too long as it's + filename in pam.d directory also */ +static const constexpr auto max_pam_service_name_len = 64; + +static void free_pam_response(struct pam_response **resp, int n) { + for (int i = 0; i < n; i++) { + free((*resp)[i].resp); + } + free(*resp); + *resp = nullptr; +} + +static int vio_server_conv(int num_msg, const struct pam_message **msg, + struct pam_response **resp, void *appdata_ptr) { + struct pam_conv_data *data = (struct pam_conv_data *)appdata_ptr; + + if (data == nullptr) { + MY_ASSERT_UNREACHABLE(); + return PAM_CONV_ERR; + } + + *resp = (struct pam_response *)calloc(num_msg, sizeof(struct pam_response)); + if (*resp == nullptr) return PAM_BUF_ERR; + + void *talk_data; + int error = auth_pam_client_talk_init(&talk_data); + if (error != PAM_SUCCESS) { + free_pam_response(resp, 0); + return error; + } + + for (int i = 0; i < num_msg; i++) { + if (!valid_pam_msg_style(msg[i]->msg_style)) { + auth_pam_client_talk_finalize(talk_data); + free_pam_response(resp, i); + return PAM_CONV_ERR; + } + + error = auth_pam_talk_perform(msg[i], &(*resp)[i], data, talk_data); + if (error != PAM_SUCCESS) { + auth_pam_client_talk_finalize(talk_data); + free_pam_response(resp, i); + return error; + } + } + auth_pam_client_talk_finalize(talk_data); + return PAM_SUCCESS; +} + +int authenticate_user_with_pam_server(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info) { + /* Set service name as specified in auth_string. If no auth_string + provided or parsing error occurs, then keep default value */ + char service_name[max_pam_service_name_len]; + strcpy(service_name, service_name_default); + if (info->auth_string) + mapping_get_service_name(service_name, sizeof(service_name), + info->auth_string); + + info->password_used = PASSWORD_USED_NO_MENTION; + + pam_handle_t *pam_handle; + struct pam_conv_data data = {vio, info}; + struct pam_conv conv_func_info = {&vio_server_conv, &data}; + int error = + pam_start(service_name, info->user_name, &conv_func_info, &pam_handle); + if (error != PAM_SUCCESS) return CR_ERROR; + + error = pam_set_item(pam_handle, PAM_RUSER, info->user_name); + if (error != PAM_SUCCESS) { + pam_end(pam_handle, error); + return CR_ERROR; + } + + error = pam_set_item(pam_handle, PAM_RHOST, info->host_or_ip); + if (error != PAM_SUCCESS) { + pam_end(pam_handle, error); + return CR_ERROR; + } + + error = pam_authenticate(pam_handle, 0); + if (error != PAM_SUCCESS) { + pam_end(pam_handle, error); + return CR_ERROR; + } + + error = pam_acct_mgmt(pam_handle, 0); + if (error != PAM_SUCCESS) { + pam_end(pam_handle, error); + return CR_ERROR; + } + + /* Get the authenticated user name from PAM */ + char *pam_mapped_user_name; + error = pam_get_item(pam_handle, PAM_USER, + const_cast( + reinterpret_cast(&pam_mapped_user_name))); + if (error != PAM_SUCCESS) { + pam_end(pam_handle, error); + return CR_ERROR; + } + + /* Check if user name from PAM is the same as provided for MySQL. If + different, use the new user name for MySQL authorization and as + CURRENT_USER() value. */ + if (strcmp(info->user_name, pam_mapped_user_name)) { + strncpy(info->authenticated_as, pam_mapped_user_name, + MYSQL_USERNAME_LENGTH); + info->authenticated_as[MYSQL_USERNAME_LENGTH] = '\0'; + } + + if (info->auth_string) { + mapping_lookup_user(pam_mapped_user_name, info->authenticated_as, + MYSQL_USERNAME_LENGTH, info->auth_string); + } + + error = pam_end(pam_handle, error); + if (error != PAM_SUCCESS) return CR_ERROR; + + return CR_OK; +} + +int auth_pam_generate_auth_string_hash(char *outbuf, unsigned int *buflen, + const char *inbuf, + unsigned int inbuflen) { + /* + fail if buffer specified by server cannot be copied to output buffer + */ + if (*buflen < inbuflen) return 1; /* error */ + strncpy(outbuf, inbuf, inbuflen); + *buflen = strlen(inbuf); + return 0; /* success */ +} + +int auth_pam_validate_auth_string_hash(char *const buf __attribute__((unused)), + unsigned int len + __attribute__((unused))) { + return 0; /* success */ +} + +int auth_pam_set_salt(const char *password __attribute__((unused)), + unsigned int password_len __attribute__((unused)), + unsigned char *salt __attribute__((unused)), + unsigned char *salt_len) { + *salt_len = 0; + return 0; /* success */ +} diff --git a/plugin/percona-pam-for-mysql/src/auth_pam_common.h b/plugin/percona-pam-for-mysql/src/auth_pam_common.h new file mode 100644 index 000000000000..9976adebe238 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/auth_pam_common.h @@ -0,0 +1,82 @@ +#ifndef AUTH_PAM_COMMON_INCLUDED +#define AUTH_PAM_COMMON_INCLUDED +/* + (C) 2012 Percona Inc. + + 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 02110-1301, USA +*/ + +/** + @file + + PAM authentication for MySQL, common definitions for side plugins. + + For the general description, see the top comment in auth_pam_common.c. +*/ + +#include +#include +#ifdef HAVE_SECURITY_PAM_MISC_H +#include +#elif defined(HAVE_SECURITY_OPENPAM_H) +#include +#endif + +#include "mysql/client_plugin.h" +#include "mysql/plugin.h" +#include "mysql/plugin_auth.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct pam_conv_data { + MYSQL_PLUGIN_VIO *vio; + MYSQL_SERVER_AUTH_INFO *info; +}; + +extern MYSQL_PLUGIN auth_pam_plugin_info; + +extern PSI_memory_key key_memory_pam_mapping_iter; +extern PSI_memory_key key_memory_pam_group_iter; + +void auth_pam_common_init(const char *psi_category); + +/** Define following three functions for your specific client plugin */ + +int auth_pam_client_talk_init(void **talk_data); + +int auth_pam_talk_perform(const struct pam_message *msg, + struct pam_response *resp, struct pam_conv_data *data, + void *talk_data); + +void auth_pam_client_talk_finalize(void *talk_data); + +int authenticate_user_with_pam_server(MYSQL_PLUGIN_VIO *vio, + MYSQL_SERVER_AUTH_INFO *info); + +int auth_pam_generate_auth_string_hash(char *outbuf, unsigned int *buflen, + const char *inbuf, + unsigned int inbuflen); + +int auth_pam_validate_auth_string_hash(char *const buf, unsigned int len); + +int auth_pam_set_salt(const char *password, unsigned int password_len, + unsigned char *salt, unsigned char *salt_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugin/percona-pam-for-mysql/src/auth_pam_compat.cc b/plugin/percona-pam-for-mysql/src/auth_pam_compat.cc new file mode 100644 index 000000000000..c855f5f8f49c --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/auth_pam_compat.cc @@ -0,0 +1,129 @@ +/* +(C) 2012, 2015 Percona LLC and/or its affiliates + +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 02110-1301, USA +*/ + +/** + @file + + PAM authentication for MySQL, server-side plugin for the + production use. + + Oracle MySQL-compatible plugin. Acts as a mediator + between the MySQL server, the MySQL client, and the PAM backend. + + The server plugin requests authentication from the PAM backend, and reads one + phrase from client plugin. mysql_clear_password plugin used as client plugin. + + This plugin does not encrypt the communication channel in any way. If this is + required, a SSL connection should be used. + + To install this plugin, copy the .so file to the plugin directory and do + + INSTALL PLUGIN auth_pam SONAME 'auth_pam_compat.so'; + + To use this plugin for one particular user, specify it at user's creation time + (TODO: tested with localhost only): + + CREATE USER 'username'@'hostname' IDENTIFIED WITH auth_pam_compat; + + Alternatively UPDATE the mysql.user table to set the plugin value for an + existing user. + + Also it is possible to use this plugin to authenticate anonymous users: + + CREATE USER ''@'hostname' IDENTIFIED WITH auth_pam_compat; + +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include "auth_pam_common.h" +#include "my_sys.h" + +MYSQL_PLUGIN auth_pam_plugin_info; + +int auth_pam_client_talk_init(void **talk_data) { + int *num_talks = static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, sizeof(int), MY_ZEROFILL)); + *talk_data = (void *)num_talks; + return (num_talks != nullptr) ? PAM_SUCCESS : PAM_BUF_ERR; +} + +void auth_pam_client_talk_finalize(void *talk_data) { my_free(talk_data); } + +int auth_pam_talk_perform(const struct pam_message *msg, + struct pam_response *resp, struct pam_conv_data *data, + void *talk_data) { + if (msg->msg_style == PAM_PROMPT_ECHO_OFF || + msg->msg_style == PAM_PROMPT_ECHO_ON) { + /* mysql_clear_password plugin has support for only single phrase */ + int *num_talks = (int *)talk_data; + if (*num_talks > 1) return PAM_CONV_ERR; + + /* Read the answer */ + unsigned char *pkt; + int pkt_len = data->vio->read_packet(data->vio, &pkt); + if (pkt_len < 0) return PAM_CONV_ERR; + + resp->resp = static_cast(malloc(pkt_len + 1)); + if (resp->resp == nullptr) return PAM_BUF_ERR; + + strncpy(resp->resp, (char *)pkt, pkt_len); + resp->resp[pkt_len] = '\0'; + + /** + we could only guess whether password was used or not + normally we would set PASSWORD_USED_NO_MENTION but + because of http://bugs.mysql.com/bug.php?id=72536 + we set PASSWORD_USED_YES. + */ + data->info->password_used = PASSWORD_USED_YES; + ++(*num_talks); + } + + return PAM_SUCCESS; +} + +static int auth_pam_compat_init(MYSQL_PLUGIN plugin_info) { + auth_pam_common_init("auth_pam_compat"); + auth_pam_plugin_info = plugin_info; + return 0; +} + +static struct st_mysql_auth pam_auth_handler = { + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "mysql_clear_password", + &authenticate_user_with_pam_server, + &auth_pam_generate_auth_string_hash, + &auth_pam_validate_auth_string_hash, + &auth_pam_set_salt, + 0UL, + nullptr}; + +mysql_declare_plugin(auth_pam) { + MYSQL_AUTHENTICATION_PLUGIN, &pam_auth_handler, "auth_pam_compat", + "Percona, Inc.", "PAM authentication plugin", PLUGIN_LICENSE_GPL, + auth_pam_compat_init, nullptr, nullptr, 0x0001, nullptr, nullptr, nullptr +#if MYSQL_PLUGIN_INTERFACE_VERSION >= 0x103 + , + 0 +#endif +} +mysql_declare_plugin_end; diff --git a/plugin/percona-pam-for-mysql/src/dialog.cc b/plugin/percona-pam-for-mysql/src/dialog.cc new file mode 100644 index 000000000000..e676ab046d19 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/dialog.cc @@ -0,0 +1,327 @@ +/* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** + @file + + dialog client authentication plugin with examples + + dialog is a general purpose client authentication plugin, it simply + asks the user the question, as provided by the server and reports + the answer back to the server. No encryption is involved, + the answers are sent in clear text. + + Two examples are provided: two_questions server plugin, that asks + the password and an "Are you sure?" question with a reply "yes, of course". + It demonstrates the usage of "password" (input is hidden) and "ordinary" + (input can be echoed) questions, and how to mark the last question, + to avoid an extra roundtrip. + + And three_attempts plugin that gives the user three attempts to enter + a correct password. It shows the situation when a number of questions + is not known in advance. +*/ +#if defined(WIN32) && !defined(RTLD_DEFAULT) +#define RTLD_DEFAULT GetModuleHandle(NULL) +#endif + +#include +#include +#include +#include "mysql.h" +#include "mysql/client_plugin.h" +#include "mysql/plugin_auth.h" + +#ifdef HAVE_DLFCN_H +#include +#endif + +#if !defined(_GNU_SOURCE) +#define _GNU_SOURCE /* for RTLD_DEFAULT */ +#endif + +/** + first byte of the question string is the question "type". + It can be an "ordinary" or a "password" question. + The last bit set marks a last question in the authentication exchange. +*/ +#define ORDINARY_QUESTION "\2" +#define LAST_QUESTION "\3" +#define PASSWORD_QUESTION "\4" +#define LAST_PASSWORD "\5" + +/********************* SERVER SIDE ****************************************/ + +/** + dialog demo with two questions, one password and one, the last, ordinary. +*/ +static int two_questions(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) { + /* send a password question */ + if (vio->write_packet( + vio, + (const unsigned char *)PASSWORD_QUESTION "Password, please:", 18)) + return CR_ERROR; + + /* read the answer */ + unsigned char *pkt; + int pkt_len = vio->read_packet(vio, &pkt); + if (pkt_len < 0) return CR_ERROR; + + info->password_used = PASSWORD_USED_YES; + + /* fail if the password is wrong */ + if (strcmp((const char *)pkt, info->auth_string)) return CR_ERROR; + + /* send the last, ordinary, question */ + if (vio->write_packet( + vio, (const unsigned char *)LAST_QUESTION "Are you sure ?", 15)) + return CR_ERROR; + + /* read the answer */ + if ((pkt_len = vio->read_packet(vio, &pkt)) < 0) return CR_ERROR; + + /* check the reply */ + return strcmp((const char *)pkt, "yes, of course") ? CR_ERROR : CR_OK; +} + +static struct st_mysql_auth two_handler = { + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "dialog", /* requires dialog client plugin */ + two_questions, + nullptr, + nullptr, + nullptr, + 0UL, + nullptr}; + +/* dialog demo where the number of questions is not known in advance */ +static int three_attempts(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) { + for (int i = 0; i < 3; i++) { + /* send the prompt */ + if (vio->write_packet( + vio, + (const unsigned char *)PASSWORD_QUESTION "Password, please:", 18)) + return CR_ERROR; + + /* read the password */ + unsigned char *pkt; + int pkt_len = vio->read_packet(vio, &pkt); + if (pkt_len < 0) return CR_ERROR; + + info->password_used = PASSWORD_USED_YES; + + /* + finish, if the password is correct. + note, that we did not mark the prompt packet as "last" + */ + if (strcmp((const char *)pkt, info->auth_string) == 0) return CR_OK; + } + + return CR_ERROR; +} + +static struct st_mysql_auth three_handler = { + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "dialog", /* requires dialog client plugin */ + three_attempts, + nullptr, + nullptr, + nullptr, + 0UL, + nullptr}; + +mysql_declare_plugin(dialog){ + MYSQL_AUTHENTICATION_PLUGIN, + &two_handler, + "two_questions", + "Sergei Golubchik", + "Dialog plugin demo 1", + PLUGIN_LICENSE_GPL, + nullptr, + nullptr, + nullptr, + 0x0100, + nullptr, + nullptr, + nullptr, + 0, +}, + { + MYSQL_AUTHENTICATION_PLUGIN, + &three_handler, + "three_attempts", + "Sergei Golubchik", + "Dialog plugin demo 2", + PLUGIN_LICENSE_GPL, + nullptr, + nullptr, + nullptr, + 0x0100, + nullptr, + nullptr, + nullptr, + 0, + } mysql_declare_plugin_end; + +/********************* CLIENT SIDE ***************************************/ +/* + This plugin performs a dialog with the user, asking questions and + reading answers. Depending on the client it may be desirable to do it + using GUI, or console, with or without curses, or read answers + from a smartcard, for example. + + To support all this variety, the dialog plugin has a callback function + "authentication_dialog_ask". If the client has a function of this name + dialog plugin will use it for communication with the user. Otherwise + a default fgets() based implementation will be used. +*/ + +/** + type of the mysql_authentication_dialog_ask function + + @param mysql mysql + @param type type of the input + 1 - ordinary string input + 2 - password string + @param prompt prompt + @param buf a buffer to store the use input + @param buf_len the length of the buffer + + @retval a pointer to the user input string. + It may be equal to 'buf' or to 'mysql->password'. + In all other cases it is assumed to be an allocated + string, and the "dialog" plugin will free() it. +*/ +typedef char *(*mysql_authentication_dialog_ask_t)(MYSQL *mysql, int type, + const char *prompt, + char *buf, int buf_len); + +static mysql_authentication_dialog_ask_t ask; + +static char *builtin_ask(MYSQL *mysql [[maybe_unused]], + int type [[maybe_unused]], const char *prompt, + char *buf, int buf_len) { + if (type == 2) /* password */ + { + char *const password = get_tty_password(prompt); + strncpy(buf, password, buf_len - 1); + buf[buf_len - 1] = 0; + free(password); + } else { + if (!fgets(buf, buf_len - 1, stdin)) + buf[0] = 0; + else { + const int len = strlen(buf); + if (len && buf[len - 1] == '\n') buf[len - 1] = 0; + } + } + + return buf; +} + +/** + The main function of the dialog plugin. + + Read the prompt, ask the question, send the reply, repeat until + the server is satisfied. + + @note + 1. this plugin shows how a client authentication plugin + may read a MySQL protocol OK packet internally - which is important + where a number of packets is not known in advance. + 2. the first byte of the prompt is special. it is not + shown to the user, but signals whether it is the last question + (prompt[0] & 1 == 1) or not last (prompt[0] & 1 == 0), + and whether the input is a password (not echoed). + 3. the prompt is expected to be sent zero-terminated +*/ +static int perform_dialog(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { + unsigned char cmd = 0; + char reply_buf[1024]; + bool first = true; + + do { + /* read the prompt */ + unsigned char *pkt; + int pkt_len = vio->read_packet(vio, &pkt); + if (pkt_len < 0) return CR_ERROR; + + char *reply; + if (pkt == nullptr && first) { + /* + in mysql_change_user() the client sends the first packet, so + the first vio->read_packet() does nothing (pkt == 0). + + We send the "password", assuming the client knows what it's doing. + (in other words, the dialog plugin should be only set as a default + authentication plugin on the client if the first question + asks for a password - which will be sent in clear text, by the way) + */ + reply = mysql->passwd; + } else { + cmd = *pkt++; + + /* is it MySQL protocol packet ? */ + if (cmd == 0 || cmd == 254) + return CR_OK_HANDSHAKE_COMPLETE; /* yes. we're done */ + + /* + asking for a password in the first packet mean mysql->password, if it's + set otherwise we ask the user and read the reply + */ + if ((cmd >> 1) == 2 && first && mysql->passwd[0]) + reply = mysql->passwd; + else + reply = ask(mysql, cmd >> 1, (const char *)pkt, reply_buf, + sizeof(reply_buf)); + if (!reply) return CR_ERROR; + } + /* send the reply to the server */ + const int res = + vio->write_packet(vio, (const unsigned char *)reply, strlen(reply) + 1); + + if (reply != mysql->passwd && reply != reply_buf) free(reply); + + if (res) return CR_ERROR; + + /* repeat unless it was the last question */ + first = false; + } while ((cmd & 1) != 1); + + /* the job of reading the ok/error packet is left to the server */ + return CR_OK; +} + +/** + initialization function of the dialog plugin + + Pick up the client's authentication_dialog_ask() function, if exists, + or fall back to the default implementation. +*/ + +static int init_dialog(char *unused1 [[maybe_unused]], + size_t unused2 [[maybe_unused]], + int unused3 [[maybe_unused]], + va_list unused4 [[maybe_unused]]) { + void *sym = dlsym(RTLD_DEFAULT, "mysql_authentication_dialog_ask"); + ask = sym ? (mysql_authentication_dialog_ask_t)sym : builtin_ask; + return 0; +} + +mysql_declare_client_plugin(AUTHENTICATION) "dialog", "Sergei Golubchik", + "Dialog Client Authentication Plugin", {0, 1, 0}, "GPL", + nullptr, init_dialog, nullptr, nullptr, nullptr, perform_dialog, + nullptr, mysql_end_client_plugin; diff --git a/plugin/percona-pam-for-mysql/src/groups.cc b/plugin/percona-pam-for-mysql/src/groups.cc new file mode 100644 index 000000000000..27b9f1f8114d --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/groups.cc @@ -0,0 +1,146 @@ +/* +(C) 2013, 2016 Percona LLC and/or its affiliates + +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 02110-1301, USA +*/ + +#include +#include +#include +#include +#include +#include + +#include "auth_pam_common.h" + +#include "my_sys.h" + +static int gr_buf_size = 0; + +#ifdef __APPLE__ +using my_gid_t = int; +#else +using my_gid_t = gid_t; +#endif + +/** Groups iterator. It's not exposed outsude */ +struct groups_iter { + char *buf; + my_gid_t *groups; + int current_group; + int ngroups; + int buf_size; +}; + +/** Create iterator through user groups. + Initially iterator set to position before first + group. On success non-NULL pointer returned, otherwise NULL */ +struct groups_iter *groups_iter_new(const char *user_name) { + if (gr_buf_size <= 0) { + long gr_size_max, pw_size_max; + gr_size_max = sysconf(_SC_GETGR_R_SIZE_MAX); + pw_size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + gr_buf_size = gr_size_max > pw_size_max ? gr_size_max : pw_size_max; + } + + struct groups_iter *const it = (struct groups_iter *)my_malloc( + key_memory_pam_group_iter, sizeof(struct groups_iter), + MYF(MY_FAE | MY_ZEROFILL)); + + it->buf_size = gr_buf_size; + if (it->buf_size <= 0) it->buf_size = 1024; + + it->buf = + (char *)my_malloc(key_memory_pam_group_iter, it->buf_size, MYF(MY_FAE)); + + struct passwd pwd, *pwd_result; + int error; + while ((error = getpwnam_r(user_name, &pwd, it->buf, it->buf_size, + &pwd_result)) == ERANGE) { + it->buf_size = it->buf_size * 2; + it->buf = (char *)my_realloc(key_memory_pam_group_iter, it->buf, + it->buf_size, MYF(MY_FAE)); + } + if (error != 0 || pwd_result == nullptr) { + my_plugin_log_message(&auth_pam_plugin_info, MY_ERROR_LEVEL, + "Unable to obtain the passwd entry for the user " + "'%s'.", + user_name); + my_free(it->buf); + my_free(it); + return nullptr; + } + + gr_buf_size = it->buf_size; + + it->ngroups = 1024; + it->groups = static_cast(my_malloc( + key_memory_pam_group_iter, it->ngroups * sizeof(gid_t), MYF(MY_FAE))); + error = getgrouplist(user_name, pwd_result->pw_gid, it->groups, &it->ngroups); + if (error == -1) { + it->groups = static_cast( + my_realloc(key_memory_pam_group_iter, it->groups, + it->ngroups * sizeof(gid_t), MYF(MY_FAE))); + error = + getgrouplist(user_name, pwd_result->pw_gid, it->groups, &it->ngroups); + if (error == -1) { + my_plugin_log_message(&auth_pam_plugin_info, MY_ERROR_LEVEL, + "Unable to obtain the group access list for " + "the user '%s'.", + user_name); + my_free(it->buf); + my_free(it->groups); + my_free(it); + return nullptr; + } + } + + return it; +} + +/** Move iterator to next group. + On success group name is returned, + otherwise NULL */ +const char *groups_iter_next(struct groups_iter *it) { + if (it->current_group >= it->ngroups) return nullptr; + + int error; + struct group grp, *grp_result; + while ((error = getgrgid_r(it->groups[it->current_group], &grp, it->buf, + it->buf_size, &grp_result)) == ERANGE) { + it->buf_size = it->buf_size * 2; + it->buf = (char *)my_realloc(key_memory_pam_group_iter, it->buf, + it->buf_size, MYF(MY_FAE)); + } + if (error != 0 || grp_result == nullptr) { + my_plugin_log_message(&auth_pam_plugin_info, MY_ERROR_LEVEL, + "Unable to obtain the group record for the group " + "id %d.", + (int)it->groups[it->current_group]); + return nullptr; + } + ++it->current_group; + + return grp_result->gr_name; +} + +/** Make iterator to point to the beginning again */ +void groups_iter_reset(struct groups_iter *it) { it->current_group = 0; } + +/** Finish iteration and release iterator */ +void groups_iter_free(struct groups_iter *it) { + my_free(it->buf); + my_free(it->groups); + my_free(it); +} diff --git a/plugin/percona-pam-for-mysql/src/groups.h b/plugin/percona-pam-for-mysql/src/groups.h new file mode 100644 index 000000000000..30b937468aeb --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/groups.h @@ -0,0 +1,45 @@ +#ifndef AUTH_PAM_GROUPS_INCLUDED +#define AUTH_PAM_GROUPS_INCLUDED +/* + (C) 2013 Percona LLC and/or its affiliates + + 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 02110-1301, USA +*/ + +/** + @file + + PAM authentication for MySQL, interface for groups enumeration. + +*/ + +struct groups_iter; + +/** Create iterator through user groups. + Initially iterator set to position before first + group. On success non-NULL pointer returned, otherwise NULL */ +struct groups_iter *groups_iter_new(const char *user_name); + +/** Move iterator to next group. + On success group name is returned, + otherwise NULL */ +const char *groups_iter_next(struct groups_iter *it); + +/** Make iterator to point to beginning again */ +void groups_iter_reset(struct groups_iter *it); + +/** Finish iteration and release iterator */ +void groups_iter_free(struct groups_iter *it); + +#endif diff --git a/plugin/percona-pam-for-mysql/src/lib_auth_pam_client.c b/plugin/percona-pam-for-mysql/src/lib_auth_pam_client.c new file mode 100644 index 000000000000..caed7563391a --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/lib_auth_pam_client.c @@ -0,0 +1,72 @@ +/* + (C) 2011 Percona Inc. + + 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 02110-1301, USA +*/ + +/** + @file + + PAM authentication for MySQL, common code for client-side plugins. + + For the general description, see the top comment in auth_pam.c. +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "lib_auth_pam_client.h" + +#include +#include + +#define MY_ASSERT_UNREACHABLE() assert(0) + +int authenticate_user_with_pam_client_common( + MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql __attribute__((unused)), + prompt_fn echoless_prompt_fn, prompt_fn echo_prompt_fn, + info_fn show_error_fn, info_fn show_info_fn) { + do { + char *buf; + int pkt_len; + + if ((pkt_len = vio->read_packet(vio, (unsigned char **)&buf)) < 0) + return CR_ERROR; + + /* The first byte is the message type, followed by the message itself. */ + + if (buf[0] == '\2' || buf[0] == '\3') { + /* '\2' - PAM_PROMPT_ECHO_OFF, '\3' - PAM_PROMPT_ECHO_ON */ + char *reply = (buf[0] == '\2') ? echoless_prompt_fn(&buf[1]) + : echo_prompt_fn(&buf[1]); + if (!reply) return CR_ERROR; + if (vio->write_packet(vio, (unsigned char *)reply, strlen(reply) + 1)) { + free(reply); + return CR_ERROR; + } + free(reply); + } else if (buf[0] == '\4') /* PAM_ERROR_MSG */ + show_error_fn(&buf[1]); + else if (buf[0] == '\5') /* PAM_TEXT_INFO */ + show_info_fn(&buf[1]); + else if (buf[0] == '\0') /* end-of-authorization */ + return CR_OK; + else + return CR_ERROR; /* Unknown! */ + } while (1); + + /* Should not come here */ + MY_ASSERT_UNREACHABLE(); + return CR_ERROR; +} diff --git a/plugin/percona-pam-for-mysql/src/lib_auth_pam_client.h b/plugin/percona-pam-for-mysql/src/lib_auth_pam_client.h new file mode 100644 index 000000000000..6aa1c7b27a41 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/lib_auth_pam_client.h @@ -0,0 +1,77 @@ +#ifndef LIB_AUTH_PAM_CLIENT_INCLUDED +#define LIB_AUTH_PAM_CLIENT_INCLUDED +/* + (C) 2011 Percona Inc. + + 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 02110-1301, USA +*/ + +/** + @file + + PAM authentication for MySQL, common definitions for client-side plugins. + + For the general description, see the top comment in auth_pam.c. +*/ + +#define STDCALL + +#include "mysql/client_plugin.h" + +/** + Callback type for functions that prompt the user for (echoed or silent) input + and return it. Should returns a pointer to malloc-allocated string, the + caller is responsible for freeing it. Should return NULL in the case of a + memory allocation or I/O error. */ +typedef char *(*prompt_fn)(const char *); + +/** + Callback type for functions that show user some info (error or notification). +*/ +typedef void (*info_fn)(const char *); + +struct st_mysql; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + Client-side PAM auth plugin implementation. + + Communicates with the server-side plugin and does user interaction using the + provided callbacks. + + @param vio TODO + @param mysql TODO + @param echoless_prompt_fn callback to use to prompt the user for non-echoed + input (e.g. password) + @param echo_prompt_fn callback to use to prompt the user for echoed input + (e.g. user name) + @param show_error_fn callback to use to show the user an error message + @param show_info_fn callback to use to show the user an informational message + + @return Authentication conversation status + @retval CR_OK the authentication dialog is completed successfully + @retval CR_ERROR the authentication dialog is aborted due to error +*/ +int authenticate_user_with_pam_client_common( + MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql, prompt_fn echoless_prompt_fn, + prompt_fn echo_prompt_fn, info_fn show_error_fn, info_fn show_info_fn); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/plugin/percona-pam-for-mysql/src/test_auth_pam_client.c b/plugin/percona-pam-for-mysql/src/test_auth_pam_client.c new file mode 100644 index 000000000000..875068e30eb7 --- /dev/null +++ b/plugin/percona-pam-for-mysql/src/test_auth_pam_client.c @@ -0,0 +1,69 @@ +/* + (C) 2011 Percona Inc. + + 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 02110-1301, USA +*/ + +/** + @file + + PAM authentication for MySQL, the test version of the client-side plugin. +*/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#define STDCALL + +#include "mysql/client_plugin.h" +#include "mysql/plugin_auth.h" + +#include "lib_auth_pam_client.h" + +const char *echo_off_reply_1 = "aaaaaaa"; +const char *echo_off_reply_2 = "AAAAAAA"; + +const char *echo_on_reply_1 = "bbbbbbbbbb"; +const char *echo_on_reply_2 = "BBBBBBBBBB"; + +/* Returns alternating echo_off_reply_1 and echo_off_reply_2 */ +static char *test_prompt_echo_off(const char *prompt __attribute__((unused))) { + static unsigned call_no = 0; + return strdup((call_no++ % 2) == 0 ? echo_off_reply_1 : echo_off_reply_2); +} + +/* Returns alternating echo_on_reply_1 and echo_on_reply_2 */ +static char *test_prompt_echo_on(const char *prompt __attribute__((unused))) { + static unsigned call_no = 0; + return strdup((call_no++ % 2) == 0 ? echo_on_reply_1 : echo_on_reply_2); +} + +/* Pretend we have shown the message to the user */ +static void test_show_anything(const char *message __attribute__((unused))) {} + +static int test_pam_auth_client(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql) { + return authenticate_user_with_pam_client_common( + vio, mysql, &test_prompt_echo_off, &test_prompt_echo_on, + &test_show_anything, &test_show_anything); +} + +mysql_declare_client_plugin(AUTHENTICATION) "auth_pam_test", "Percona, Inc.", + "Test version of the client PAM authentication plugin. " + "DO NOT USE IN PRODUCTION.", + {0, 1, 0}, "GPL", NULL, NULL, /* init */ + NULL, /* deinit */ + NULL, /* options */ + &test_pam_auth_client mysql_end_client_plugin; diff --git a/plugin/percona-pam-for-mysql/test/dbqp/percona_tests/percona_pam/pam_mapping_test.py b/plugin/percona-pam-for-mysql/test/dbqp/percona_tests/percona_pam/pam_mapping_test.py new file mode 100755 index 000000000000..03f84afe5209 --- /dev/null +++ b/plugin/percona-pam-for-mysql/test/dbqp/percona_tests/percona_pam/pam_mapping_test.py @@ -0,0 +1,118 @@ +#! /usr/bin/env python +# -*- mode: python; indent-tabs-mode: nil; -*- +# vim:expandtab:shiftwidth=2:tabstop=2:smarttab: +# +# Copyright (C) 2013 Percona LLC and/or its affiliates +# +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import time +import shutil +import signal +import subprocess +import re +import grp + +from lib.util.mysqlBaseTestCase import mysqlBaseTestCase +from lib.util.mysql_methods import execute_cmd + + +server_requirements = [[]] +servers = [] +server_manager = None +test_executor = None +pamcfg = '/etc/pam.d/mysqld' + +def group_exists(groupname): + try: + grp.getgrnam(groupname)[0] + except KeyError: + return False + return True + +class basicTest(mysqlBaseTestCase): + + def test_pam_basic(self): + percent_string = '%' + opt_matrix_req = ['pam_plugin_dir'] + self.servers = servers + logging = test_executor.logging + master_server = servers[0] + output_path = os.path.join(master_server.vardir, 'pam.out') + test_executor.matrix_manager.matrix_check_req(opt_matrix_req) + # This is a master + if test_executor.matrix_manager.option_matrix['pam_user']: + pam_user = test_executor.matrix_manager.option_matrix['pam_user'] + else: + pam_user = 'pamuser' + + groups = ['grp%d' % (n) for n in xrange(3)] + users = ['user1%d' % (n) for n in xrange(3)] + + for grp in groups: + if not group_exists(grp): + subprocess.call(["groupadd", grp]) + + # Create UNIX system account + if not test_executor.system_manager.user_exists(pam_user): + subprocess.call(["useradd", pam_user, "-g", groups[0], "-G", ",".join(groups[1:]) ]) + else: + subprocess.call(["usermod", "-g", groups[0], "-G", ",".join(groups[1:]), pam_user ]) + + # Create PAM config + if (os.path.isfile(pamcfg)): + os.remove(pamcfg) + + pamcfg_fh = open("/etc/pam.d/mysqld", "wb") + pamcfg_fh.write("auth\trequired\tpam_permit.so\n") + pamcfg_fh.write("account\trequired\tpam_permit.so\n") + pamcfg_fh.close(); + + master_server.stop() + + # setup plugin, users, privileges + groups.reverse() + groups = [ "grp21", "grp22" ] + groups + users = [ "usr21", "usr22" ] + users + queries = [ "INSTALL PLUGIN auth_pam SONAME 'auth_pam.so';" ] + \ + [ "CREATE USER '%s'@'localhost';" % (user) for user in users ] + \ + [ "CREATE USER ''@'' IDENTIFIED WITH auth_pam AS 'mysqld, %s';" \ + % ( ",".join([ user + "=" + group for user, group in zip(groups, users) ] ) ) ] + \ + [ "GRANT PROXY ON '%s'@'localhost' TO ''@'';" % (user) for user in users ] + \ + [ "SELECT user, host, authentication_string FROM mysql.user;", \ + "FLUSH PRIVILEGES;", "SHOW VARIABLES LIKE 'plugin%'" ] + + master_server.server_options.append('--plugin-dir=%s' %(test_executor.matrix_manager.option_matrix['pam_plugin_dir'])) + + master_server.start() + self.assertEqual( master_server.status, 1, msg = 'Server failed to restart') + + cmd = "%s --protocol=tcp --port=%d -uroot -e \"%s\"" %(master_server.mysql_client + , master_server.master_port + , "\n".join(queries) ) + retcode, output = execute_cmd(cmd, output_path, None, True) + + query = "SELECT CONCAT(USER(), CURRENT_USER(), @@PROXY_USER) as res;" + expected_result = "res%s@localhostuser10@localhost''@''" % (pam_user) + cmd = "%s --plugin-dir=/usr/lib/mysql/plugin/ --protocol=tcp --port=%d --user=%s --password=\'\' -e \"%s\" test" %(master_server.mysql_client + , master_server.master_port + , pam_user + , query ) + retcode, output = execute_cmd(cmd, output_path, None, True) + output = re.sub(r'\s+', '', output) + self.assertEqual(retcode, 0, msg = output) + self.assertEqual(output, expected_result, msg = "%s || %s" %(output, expected_result)) diff --git a/plugin/semisync/semisync_replica.cc b/plugin/semisync/semisync_replica.cc index 2c4ea3dcbb0a..6f449194e1fd 100644 --- a/plugin/semisync/semisync_replica.cc +++ b/plugin/semisync/semisync_replica.cc @@ -112,7 +112,7 @@ int ReplSemiSyncSlave::slaveReply(MYSQL *mysql, const char *binlog_filename, function_enter(kWho); DBUG_EXECUTE_IF("rpl_semisync_before_send_ack", { - const char act[] = "now SIGNAL sending_ack WAIT_FOR continue"; + const char act[] = "now WAIT_FOR continue"; assert(opt_debug_sync_timeout > 0); assert(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act))); };);