diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index eb9a2e66..0fd2e562 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -32,7 +32,7 @@ jobs: 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