From b708e6dcf15d85efcff088e41c52c7bbb603358f Mon Sep 17 00:00:00 2001 From: Benjamin AIMARD Date: Sat, 28 Nov 2020 12:26:39 +0100 Subject: [PATCH] Add support of Mysql/Mariadb for the yubikey_mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support of Mysql/Mariadb for the yubikey_mapping Mise à jour sql complete, manque la documentation Final clean Add packages dependency actions: apt update Fix bool variable cause error build travis Fix job #2 Fix job #3 Fix #4 fix &null fix &null fix &null Fix line 257 %s test Apple integration test Apple integration Timeout Travis extend Timeout travis extend Fix klali comment Fix warning fix configure.ac fix configure.ac Update configure.ac Update util.c Update util.h Update util.c Update util.c Update util.c Update util.c Fix white space fix left column --- .github/workflows/codeql-analysis.yml | 3 +- .github/workflows/scan.yml | 2 +- .travis.yml | 14 +- Makefile.am | 7 +- configure.ac | 13 ++ pam_yubico.8.txt | 12 ++ pam_yubico.c | 32 ++++- tests/aux/build-and-test.sh | 3 +- util.c | 193 ++++++++++++++++++++++++++ util.h | 3 + 10 files changed, 269 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5feafb53..0fd2e562 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,8 +30,9 @@ jobs: - name: Build yubico-pam run: | + sudo apt update sudo apt install -y libykclient-dev libykpers-1-dev libyubikey-dev \ - libpam-dev help2man asciidoc-base + libpam-dev help2man asciidoc-base libmysqlclient-dev autoreconf --install ./configure make diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index bcc4b101..621e24c0 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -8,7 +8,7 @@ on: env: SCAN_IMG: yubico-yes-docker-local.jfrog.io/static-code-analysis/c:v1 - COMPILE_DEPS: "libykclient-dev libykpers-1-dev libyubikey-dev" + COMPILE_DEPS: "libykclient-dev libykpers-1-dev libyubikey-dev libmysqlclient-dev" jobs: build: diff --git a/.travis.yml b/.travis.yml index 9b5e3b4f..71cc606d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ -sudo: required language: c os: - linux @@ -7,13 +6,14 @@ compiler: - gcc - clang env: - - CONFIGURE_ARGS="" EXTRA="libldap2-dev libykpers-1-dev libnet-ldap-server-perl" - - CONFIGURE_ARGS="--without-ldap" EXTRA="libykpers-1-dev" - - CONFIGURE_ARGS="--without-cr" EXTRA="libldap2-dev libnet-ldap-server-perl" - - CONFIGURE_ARGS="--without-ldap --without-cr" + - CONFIGURE_ARGS="" EXTRA="libldap2-dev libykpers-1-dev libnet-ldap-server-perl libmysqlclient-dev" + - CONFIGURE_ARGS="--without-ldap" EXTRA="libykpers-1-dev libmysqlclient-dev" + - CONFIGURE_ARGS="--without-cr" EXTRA="libldap2-dev libnet-ldap-server-perl libmysqlclient-dev" + - CONFIGURE_ARGS="--without-ldap --without-cr" EXTRA="libmysqlclient-dev" script: tests/aux/build-and-test.sh -matrix: +jobs: + install: travis_wait 30 mvn install include: - compiler: gcc os: linux - env: COVERAGE="--enable-coverage" EXTRA="libldap2-dev libykpers-1-dev libnet-ldap-server-perl lcov" + env: COVERAGE="--enable-coverage" EXTRA="libldap2-dev libykpers-1-dev libnet-ldap-server-perl lcov libmysqlclient-dev" diff --git a/Makefile.am b/Makefile.am index 2708acef..78ffa8f4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,7 +45,12 @@ pam_yubico_la_LDFLAGS = -module -avoid-version noinst_LTLIBRARIES = libpam_util.la libpam_real.la libpam_util_la_SOURCES = util.c util.h -libpam_util_la_LIBADD = @LTLIBYUBIKEY@ @YKPERS_LIBS@ +libpam_util_la_LIBADD = @LTLIBYUBIKEY@ @YKPERS_LIBS@ + +# if MYSQL_SUPPORT +AM_CFLAGS += @MYSQL_CFLAGS@ +libpam_util_la_LIBADD += @MYSQL_LIBS@ +# endif libpam_real_la_SOURCES = pam_yubico.c diff --git a/configure.ac b/configure.ac index 38471df5..f9da7e33 100644 --- a/configure.ac +++ b/configure.ac @@ -75,6 +75,19 @@ AC_ARG_WITH([ldap], [libldap not found, will not be compiled (--without-ldap to disable ldap support)])], [])]) +AC_ARG_WITH([mysql], + [AS_HELP_STRING([--without-mysql], + [disable support for mysql])], + [], + [with_mysql=yes]) +AS_IF([test "x$with_mysql" != xno], + [ + PKG_CHECK_MODULES([MYSQL], [mysqlclient], + [AC_DEFINE([HAVE_MYSQL], [1],[Define if you have mysqlclient])], + [AC_MSG_WARN( + [libmysqlclient not found, will not be compiled (--without-mysql to disable mysql support)])]) + ]) +AM_CONDITIONAL(MYSQL_SUPPORT,test "x$with_mysql" != xno) AC_LIB_HAVE_LINKFLAGS([ykclient],, [#include ], [ykclient_set_proxy(0, 0)]) diff --git a/pam_yubico.8.txt b/pam_yubico.8.txt index 9bc9abca..45bd6b73 100644 --- a/pam_yubico.8.txt +++ b/pam_yubico.8.txt @@ -116,6 +116,18 @@ CA certitificate file for the LDAP connection. *chalresp_path*=_path_:: Path of a system-wide directory where challenge-response files can be found for users. Default location is `$HOME/.yubico/`. +*mysql_server*=_mysqlserver_:: +Hostname/Adress of mysql server. Example 10.0.0.1 + +*mysql_user*=_mysqluser_:: +User for accessing to the database. Strongly recommended to use a specific user with read only access. + +*mysql_password*=_mysqlpassword_:: +Mysql password associated to the user. + +*mysql_database*=_mysqldatabase_:: +the name of the database. Example : otp + == EXAMPLES auth sufficient pam_yubico.so id=16 debug diff --git a/pam_yubico.c b/pam_yubico.c index cf277953..899dc5b3 100644 --- a/pam_yubico.c +++ b/pam_yubico.c @@ -134,6 +134,11 @@ struct cfg const char *user_attr; const char *yubi_attr; const char *yubi_attr_prefix; + const char *mysql_server; + const char *mysql_user; + const char *mysql_password; + const char *mysql_database; + unsigned int token_id_length; enum key_mode mode; const char *chalresp_path; @@ -164,8 +169,19 @@ authorize_user_token (struct cfg *cfg, pam_handle_t *pamh) { int retval = AUTH_ERROR; - - if (cfg->auth_file) + if (cfg->mysql_server) + { +#ifdef HAVE_MYSQL + /* Administrator had configured the database and specified is name + as an argument for this module. + */ + DBG ("Using Mariadb or Mysql Database"); + retval = check_user_token_mysql(cfg->mysql_server, cfg->mysql_user, cfg->mysql_password, cfg->mysql_database, username, otp_id, cfg->debug, cfg->debug_file); +#else + DBG (("Trying to use MYSQL, but this function is not compiled in pam_yubico!!")); +#endif + } + else if (cfg->auth_file) { /* Administrator had configured the file and specified is name as an argument for this module. @@ -874,6 +890,15 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) cfg->mode = CLIENT; if (strncmp (argv[i], "chalresp_path=", 14) == 0) cfg->chalresp_path = argv[i] + 14; + if (strncmp (argv[i], "mysql_server=", 13) == 0) + cfg->mysql_server = argv[i] + 13; + if (strncmp (argv[i], "mysql_user=", 11) == 0) + cfg->mysql_user = argv[i] + 11; + if (strncmp (argv[i], "mysql_password=", 15) == 0) + cfg->mysql_password = argv[i] + 15; + if (strncmp (argv[i], "mysql_database=", 15) == 0) + cfg->mysql_database = argv[i] + 15; + if (strncmp (argv[i], "debug_file=", 11) == 0) { const char *filename = argv[i] + 11; @@ -939,6 +964,9 @@ parse_cfg (int flags, int argc, const char **argv, struct cfg *cfg) DBG ("token_id_length=%u", cfg->token_id_length); DBG ("mode=%s", cfg->mode == CLIENT ? "client" : "chresp" ); DBG ("chalresp_path=%s", cfg->chalresp_path ? cfg->chalresp_path : "(null)"); + DBG ("mysql_server=%s", cfg->mysql_server ? cfg->mysql_server : "(null)"); + DBG ("mysql_user=%s", cfg->mysql_user ? cfg->mysql_user : "(null)"); + DBG ("mysql_database=%s", cfg->mysql_database ? cfg->mysql_database : "(null)"); if (fd != -1) close(fd); diff --git a/tests/aux/build-and-test.sh b/tests/aux/build-and-test.sh index b239d32c..3db2cbc2 100755 --- a/tests/aux/build-and-test.sh +++ b/tests/aux/build-and-test.sh @@ -7,7 +7,7 @@ autoreconf -i if [ "x$TRAVIS_OS_NAME" != "xosx" ]; then sudo add-apt-repository -y ppa:yubico/stable sudo apt-get update -qq || true - sudo apt-get install -qq -y --no-install-recommends libykclient-dev libpam0g-dev libyubikey-dev asciidoc docbook-xsl xsltproc libxml2-utils $EXTRA + sudo apt-get install -qq -y --no-install-recommends libykclient-dev libpam0g-dev libyubikey-dev asciidoc docbook-xsl xsltproc libxml2-utils libmysqlclient-dev $EXTRA else brew update brew install pkg-config @@ -17,6 +17,7 @@ else brew install libyubikey brew install ykclient brew install ykpers + brew install mysql-connector-c #Mysql cpanp install Net::LDAP::Server # this is required so asciidoc can find the xml catalog diff --git a/util.c b/util.c index 1b18196a..c1d91e39 100644 --- a/util.c +++ b/util.c @@ -52,6 +52,10 @@ #include #endif /* HAVE_CR */ +#ifdef HAVE_MYSQL +#include +#endif + int get_user_cfgfile_path(const char *common_path, const char *filename, const struct passwd *user, char **fn) { @@ -93,6 +97,195 @@ get_user_cfgfile_path(const char *common_path, const char *filename, const struc return 1; } +#ifdef HAVE_MYSQL +/* + * This function will look for users name with valid user token id, in a database Mysql + * + * Returns one of AUTH_FOUND, AUTH_NOT_FOUND, AUTH_NO_TOKENS, AUTH_ERROR. + * + * Need database with this table structure: + * + * CREATE TABLE IF NOT EXISTS `otp`.`yubikey_mappings`( + * `otp_id` VARCHAR(12) NOT NULL , + * `username` VARCHAR(64) NOT NULL , + * PRIMARY KEY (`otp_id`(12)) + * ); + * + */ +int +check_user_token_mysql(const char *mysql_server, + const char *mysql_user, + const char *mysql_password, + const char *mysql_database, + const char *username, + const char *otp_id, + int verbose, + FILE *debug_file) +{ + + int retval = AUTH_ERROR; + int fd; + struct stat st; + FILE *opwfile; + MYSQL *con = NULL; + MYSQL_STMT *stmt; + MYSQL_BIND ps_params[2]; + MYSQL_BIND bind[1]; + long unsigned int str_username; + long unsigned int str_otp; + long unsigned int length; + int int_data; + int row_count; + bool is_null; + bool error; + + if(mysql_library_init(0, NULL, NULL)){ + if(verbose){ + D (debug_file, "could not initialize MySQL client library"); + } + + return retval; + } + + con = mysql_init(con); + if(!con) { + if(verbose) + D (debug_file, "out of memorys"); + return retval; + } + + if(mysql_real_connect(con, mysql_server,mysql_user,mysql_password,mysql_database, 0, NULL, 0) == NULL) + { + if(verbose) + D (debug_file, "Connection failed ..."); + return retval; + } + + stmt = mysql_stmt_init(con); + if(!stmt) + { + if(verbose) + D (debug_file, "Connection failed ... 2"); + return retval; + } + + const char *sql = "SELECT count(username) FROM yubikey_mappings WHERE username = ?;"; + const char *sql2 = "SELECT count(username) FROM yubikey_mappings WHERE username = ? and otp_id = ?;"; + + if(otp_id == NULL) + { + if(mysql_stmt_prepare(stmt, sql, strlen(sql))) + { + if(verbose) + D (debug_file, "mysql_stmt_prepare() failed %s", mysql_stmt_error(stmt)); + return retval; + } + }else{ + if(mysql_stmt_prepare(stmt, sql2, strlen(sql2))) + { + if(verbose) + D (debug_file, "mysql_stmt_prepare() failed %s", mysql_stmt_error(stmt)); + return retval; + } + } + + str_username = strlen(username); + memset(ps_params, 0, sizeof(ps_params)); + ps_params[0].buffer_type = MYSQL_TYPE_STRING; + ps_params[0].buffer = (char *)username; + ps_params[0].buffer_length = str_username; + ps_params[0].length = &str_username; + ps_params[0].is_null = 0; + + if(otp_id != NULL) + { + str_otp= strlen(otp_id); + ps_params[1].buffer_type = MYSQL_TYPE_STRING; + ps_params[1].buffer = (char *)otp_id; + ps_params[1].buffer_length = str_otp; + ps_params[1].length = &str_otp; + ps_params[1].is_null = 0; + } + + if(mysql_stmt_bind_param(stmt, ps_params)) + { + if(verbose) + D (debug_file, "mysql_stmt_bind_param() failed %s", mysql_stmt_error(stmt)); + return retval; + } + + if(mysql_stmt_execute(stmt)) + { + if(verbose) + D (debug_file, "mysql_stmt_execute() failed %s", mysql_stmt_error(stmt)); + return retval; + } + + memset(bind, 0, sizeof(bind)); + bind[0].buffer_type = MYSQL_TYPE_LONG; + bind[0].buffer = (char *)&int_data; + bind[0].length = &length; + bind[0].is_null = &is_null; + bind[0].error = &error; + + if(mysql_stmt_bind_result(stmt, bind)) + { + if(verbose) + D (debug_file, "mysql_stmt_bind_result() failed %s", mysql_stmt_error(stmt)); + } + + if(mysql_stmt_store_result(stmt)) + { + if(verbose) + D (debug_file, "mysql_stmt_store_result() failed %s", mysql_stmt_error(stmt)); + return retval; + } + + while(!mysql_stmt_fetch(stmt)) + { + if(is_null) + { + D (debug_file, "mysql_stmt_fetch() failed"); + } + else + { + if(otp_id != NULL){ + if(int_data) + { + return AUTH_FOUND; + } + else + { + return AUTH_NOT_FOUND; + } + } + else if(otp_id == NULL) + { + if(int_data) + { + return AUTH_NOT_FOUND; + } + else + { + return AUTH_NO_TOKENS; + } + } + } + } + + if(mysql_stmt_close(stmt)) + { + if(verbose) + D (debug_file, "mysql_stmt_close() failed %s", mysql_stmt_error(stmt)); + return retval; + } + + mysql_close(con); + mysql_library_end(); + + return retval; +} +#endif /* * This function will look for users name with valid user token id. diff --git a/util.h b/util.h index 996b0c4c..fbf5397b 100644 --- a/util.h +++ b/util.h @@ -51,6 +51,9 @@ #define AUTH_NOT_FOUND -1 /* The requested token is not associated to the user */ int get_user_cfgfile_path(const char *common_path, const char *filename, const struct passwd *user, char **fn); +#ifdef HAVE_MYSQL +int check_user_token_mysql(const char *mysql_server,const char *mysql_user,const char *mysql_password,const char *mysql_database,const char *username,const char *otp_id,int verbose,FILE *debug_file); +#endif int check_user_token(const char *authfile, const char *username, const char *otp_id, int verbose, FILE *debug_file); #if HAVE_CR