From ff09b8b8ed8ad34635f54fb2271a22cceed6e404 Mon Sep 17 00:00:00 2001 From: liuh-80 <58683130+liuh-80@users.noreply.github.com> Date: Sat, 13 Nov 2021 09:57:30 +0800 Subject: [PATCH] [TACACS+] Add Bash TACACS+ plugin for per-command authorization. (#8715) This pull request add a bash plugin for TACACS+ per-command authorization #### Why I did it 1. To support TACACS per command authorization, we check user command before execute it. 2. Fix libtacsupport.so can't parse tacplus_nss.conf correctly issue: Support debug=on setting. Support put server address and secret in same row. 3. Fix the parse_config_file method not reset server list before parse config file issue. #### How I did it The bash plugin will be called before every user command, and check user command with remote TACACS+ server for per-command authorization. #### How to verify it UT with CUnit cover all code in this plugin. Also pass all current UT. #### Which release branch to backport (provide reason below if selected) N/A #### Description for the changelog Add Bash TACACS+ plugin. #### A picture of a cute animal (not mandatory but encouraged) --- ThirdPartyLicenses.txt | 30 ++ .../build_templates/sonic_debian_extension.j2 | 5 +- rules/tacacs.dep | 12 + rules/tacacs.mk | 13 + slave.mk | 3 +- src/tacacs/.gitignore | 1 + src/tacacs/bash_tacplus/Makefile.am | 27 + src/tacacs/bash_tacplus/bash_tacplus.c | 488 ++++++++++++++++++ src/tacacs/bash_tacplus/configure.ac | 71 +++ .../bash_tacplus/debian/bash-tacplus.postinst | 14 + src/tacacs/bash_tacplus/debian/changelog | 6 + src/tacacs/bash_tacplus/debian/compat | 1 + src/tacacs/bash_tacplus/debian/control | 11 + src/tacacs/bash_tacplus/debian/rules | 27 + src/tacacs/bash_tacplus/debian/source/format | 1 + src/tacacs/bash_tacplus/unittest/Makefile.am | 14 + .../bash_tacplus/unittest/mock_helper.c | 209 ++++++++ .../bash_tacplus/unittest/mock_helper.h | 46 ++ .../bash_tacplus/unittest/plugin_test.c | 219 ++++++++ ...acacs-support-functions-into-library.patch | 89 +++- 20 files changed, 1260 insertions(+), 27 deletions(-) create mode 100644 src/tacacs/bash_tacplus/Makefile.am create mode 100644 src/tacacs/bash_tacplus/bash_tacplus.c create mode 100644 src/tacacs/bash_tacplus/configure.ac create mode 100644 src/tacacs/bash_tacplus/debian/bash-tacplus.postinst create mode 100644 src/tacacs/bash_tacplus/debian/changelog create mode 100644 src/tacacs/bash_tacplus/debian/compat create mode 100644 src/tacacs/bash_tacplus/debian/control create mode 100644 src/tacacs/bash_tacplus/debian/rules create mode 100644 src/tacacs/bash_tacplus/debian/source/format create mode 100644 src/tacacs/bash_tacplus/unittest/Makefile.am create mode 100644 src/tacacs/bash_tacplus/unittest/mock_helper.c create mode 100644 src/tacacs/bash_tacplus/unittest/mock_helper.h create mode 100644 src/tacacs/bash_tacplus/unittest/plugin_test.c diff --git a/ThirdPartyLicenses.txt b/ThirdPartyLicenses.txt index 376506337e..8941c1be7d 100644 --- a/ThirdPartyLicenses.txt +++ b/ThirdPartyLicenses.txt @@ -1170,3 +1170,33 @@ Microsoft is offering you a license to use the following components, to the exte * See the License for the specific language governing permissions and * limitations under the License. */ + + 5. src/tacacs/bash/bash_tacplus based on https://github.com/daveolson53/tacplus-auth project using GNU GENERAL PUBLIC LICENSE Version 2 + +/* Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + * Upstream-Name: tacplus-auth + * Source: https://github.com/daveolson53/tacplus-auth + * + * Files: * + * Copyright: 2016 Cumulus Networks, Inc. All rights reserved., + * 2010 Pawel Krawczyk and Jeroen Nijhof . + * License: GPL-2+ + * + * License: GPL-2+ + * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * . + * On Debian systems, the full copy of the GPL-2 license can be found in + * /usr/share/common-licenses/GPL-2 + */ \ No newline at end of file diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 3c0f18e8ac..ea1015d7ed 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -225,7 +225,7 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/sonic-utilities-data_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f # Install customized bash version to patch bash plugin support. -sudo dpkg --root=$FILESYSTEM_ROOT -i target/debs/bash_*.deb || \ +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/bash_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f # sonic-utilities-data installs bash-completion as a dependency. However, it is disabled by default @@ -274,6 +274,9 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libpam-tacplus_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-tacplus_*.deb || \ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f +# Install bash-tacplus +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/bash-tacplus_*.deb || \ + sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f # Disable tacplus by default sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove tacplus sudo sed -i -e '/^passwd/s/ tacplus//' $FILESYSTEM_ROOT/etc/nsswitch.conf diff --git a/rules/tacacs.dep b/rules/tacacs.dep index 29de9262f5..f7e807f9c4 100644 --- a/rules/tacacs.dep +++ b/rules/tacacs.dep @@ -21,3 +21,15 @@ $(LIBNSS_TACPLUS)_CACHE_MODE := GIT_CONTENT_SHA $(LIBNSS_TACPLUS)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) $(LIBNSS_TACPLUS)_DEP_FILES := $(DEP_FILES) + + + +SPATH := $($(BASH_TACPLUS)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/tacacs.mk rules/tacacs.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(SPATH)) + +$(BASH_TACPLUS)_CACHE_MODE := GIT_CONTENT_SHA +$(BASH_TACPLUS)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(BASH_TACPLUS)_DEP_FILES := $(DEP_FILES) + diff --git a/rules/tacacs.mk b/rules/tacacs.mk index 1c620ebced..f8796e6ffc 100644 --- a/rules/tacacs.mk +++ b/rules/tacacs.mk @@ -29,6 +29,19 @@ $(LIBNSS_TACPLUS)_RDEPENDS += $(LIBTAC2) $(LIBNSS_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/nss SONIC_MAKE_DEBS += $(LIBNSS_TACPLUS) + +# bash-tacplus packages +BASH_TACPLUS_VERSION = 1.0.0 + +export BASH_TACPLUS_VERSION + +BASH_TACPLUS = bash-tacplus_$(BASH_TACPLUS_VERSION)_$(CONFIGURED_ARCH).deb +$(BASH_TACPLUS)_DEPENDS += $(LIBTAC_DEV) +$(BASH_TACPLUS)_RDEPENDS += $(LIBTAC2) +$(BASH_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/bash_tacplus +SONIC_DPKG_DEBS += $(BASH_TACPLUS) + + # The .c, .cpp, .h & .hpp files under src/{$DBG_SRC_ARCHIVE list} # are archived into debug one image to facilitate debugging. # diff --git a/slave.mk b/slave.mk index 29dd672f11..0757ef6824 100644 --- a/slave.mk +++ b/slave.mk @@ -943,7 +943,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(PYTHON3_SWSSCOMMON) \ $(SONIC_UTILITIES_DATA) \ $(SONIC_HOST_SERVICES_DATA) \ - $(BASH)) \ + $(BASH) \ + $(BASH_TACPLUS)) \ $$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \ $$(addprefix $(TARGET_PATH)/,$$(SONIC_PACKAGES_LOCAL)) \ $$(addprefix $(FILES_PATH)/,$$($$*_FILES)) \ diff --git a/src/tacacs/.gitignore b/src/tacacs/.gitignore index 3e483805df..48b82082ad 100644 --- a/src/tacacs/.gitignore +++ b/src/tacacs/.gitignore @@ -1,5 +1,6 @@ * !.gitignore +!bash_tacplus/* nsm/* !nsm/Makefile !nsm/*.patch diff --git a/src/tacacs/bash_tacplus/Makefile.am b/src/tacacs/bash_tacplus/Makefile.am new file mode 100644 index 0000000000..72ad458004 --- /dev/null +++ b/src/tacacs/bash_tacplus/Makefile.am @@ -0,0 +1,27 @@ +########################################################################### +## +## File: ./Makefile.am +## Versions: $Id: Makefile.am,v 1.0 2021/08/24 12:04:29 liuh@microsoft.com Exp $ +## Created: 2021/08/24 +## +########################################################################### + +ACLOCAL_AMFLAGS = -I config +AUTOMAKE_OPTIONS = subdir-objects + +moduledir = @plugindir@ +module_LTLIBRARIES = bash_tacplus.la +bash_tacplus_la_SOURCES = bash_tacplus.h \ +bash_tacplus.c +bash_tacplus_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libtac/include +bash_tacplus_la_LDFLAGS = -module -avoid-version + +EXTRA_DIST = bash_tacplus.spec + +MAINTAINERCLEANFILES = Makefile.in config.h.in configure aclocal.m4 \ + config/config.guess config/config.sub config/depcomp \ + config/install-sh config/ltmain.sh config/missing + +pkgconfigdir = $(libdir)/pkgconfig + +SUBDIRS = unittest diff --git a/src/tacacs/bash_tacplus/bash_tacplus.c b/src/tacacs/bash_tacplus/bash_tacplus.c new file mode 100644 index 0000000000..b184b8f14b --- /dev/null +++ b/src/tacacs/bash_tacplus/bash_tacplus.c @@ -0,0 +1,488 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Remote user gecos prefix, which been assigned by nss_tacplus */ +#define REMOTE_USER_GECOS_PREFIX "remote_user" + +/* Default value for _SC_GETPW_R_SIZE_MAX */ +#define DEFAULT_SC_GETPW_R_SIZE_MAX 1024 + +/* Return value for is_local_user method */ +#define IS_LOCAL_USER 0 +#define IS_REMOTE_USER 1 +#define ERROR_CHECK_LOCAL_USER 2 + +/* Tacacs+ lib */ +#include + +/* Tacacs+ support lib */ +#include + +/* Output syslog to mock method when build with UT */ +#if defined (BASH_PLUGIN_UT) +#define syslog mock_syslog +#endif + +/* Tacacs+ log format */ +#define TACACS_LOG_FORMAT "TACACS+: %s" + +/* Tacacs+ config file timestamp string format */ +#define CONFIG_FILE_TIME_STAMP_FORMAT "%d.%m.%Y %H:%M:%S" + +/* Tacacs+ config file timestamp string length */ +#define CONFIG_FILE_TIME_STAMP_LEN 100 + +/* + Convert log to a string because va args resoursive issue: + http://www.c-faq.com/varargs/handoff.html +*/ +#define GENERATE_LOG_FROM_VA(logBufferName) \ + char logBufferName[512]; \ + va_list args; \ + va_start(args, format); \ + vsnprintf(logBufferName, sizeof(logBufferName), format, args); \ + va_end(args); + +/* Config file path */ +const char *tacacs_config_file = "/etc/tacplus_nss.conf"; + +/* Unknown user name */ +const char *unknown_username = "UNKNOWN"; + + +/* Config file attribute */ +struct stat config_file_attr; + +/* Tacacs server config data */ +typedef struct { + struct addrinfo *address; + const char *key; +} tacacs_server_t; + +/* Tacacs control flag */ +int tacacs_ctrl; + +/* + * Output error message. + */ +void output_error(const char *format, ...) +{ + GENERATE_LOG_FROM_VA(logBuffer); + + if (tacacs_ctrl & PAM_TAC_DEBUG) { + fprintf(stderr, TACACS_LOG_FORMAT, logBuffer); + } + + syslog(LOG_ERR, TACACS_LOG_FORMAT, logBuffer); +} + +/* + * Output debug message. + */ +void output_debug(const char *format, ...) +{ + if ((tacacs_ctrl & PAM_TAC_DEBUG) == 0) { + return; + } + + GENERATE_LOG_FROM_VA(logBuffer); + fprintf(stderr, TACACS_LOG_FORMAT, logBuffer); + syslog(LOG_DEBUG, TACACS_LOG_FORMAT, logBuffer); +} + + +/* + * Send authorization message. + * This method based on send_auth_msg in https://github.com/daveolson53/tacplus-auth/blob/master/tacplus-auth.c + */ +int send_authorization_message( + int tac_fd, + const char *user, + const char *tty, + const char *host, + uint16_t taskid, + const char *cmd, + char **args, + int argc) +{ + char buf[128]; + struct tac_attrib *attr; + int retval; + struct areply re; + int i; + + attr=(struct tac_attrib *)xcalloc(1, sizeof(struct tac_attrib)); + + snprintf(buf, sizeof buf, "%hu", taskid); + tac_add_attrib(&attr, "task_id", buf); + tac_add_attrib(&attr, "protocol", "ssh"); + tac_add_attrib(&attr, "service", "shell"); + + tac_add_attrib(&attr, "cmd", (char*)cmd); + + for(i=1; i= sizeof(tbuf)) { + snprintf(tbuf, sizeof tbuf, "%s", args[i]); + arg = tbuf; + } + else { + arg = args[i]; + } + + tac_add_attrib(&attr, "cmd-arg", (char *)arg); + } + + re.msg = NULL; + output_debug("send authorizatiom message with user: %s, tty: %s, host: %s\n", user, tty, host); + retval = tac_author_send(tac_fd, (char *)user, (char *)tty, (char *)host, attr); + output_debug("authorization result: %d\n", retval); + + if(retval < 0) { + output_error("send of authorization message failed: %s\n", strerror(errno)); + } + else { + retval = tac_author_read(tac_fd, &re); + if (retval < 0) { + output_debug("authorization response failed: %d\n", retval); + } + else if(re.status == AUTHOR_STATUS_PASS_ADD || + re.status == AUTHOR_STATUS_PASS_REPL) { + retval = 0; + } + else { + output_debug("command not authorized (%d)\n", re.status); + retval = 1; + } + } + + tac_free_attrib(&attr); + if(re.msg != NULL) { + free(re.msg); + } + + return retval; +} + +/* + * Send tacacs authorization request. + * This method based on send_tacacs_auth in https://github.com/daveolson53/tacplus-auth/blob/master/tacplus-auth.c + */ +int tacacs_authorization( + const char *user, + const char *tty, + const char *host, + const char *cmd, + char **args, + int argc) +{ + int result = 1, server_idx, server_fd, connected_servers=0; + uint16_t task_id = (uint16_t)getpid(); + + for(server_idx = 0; server_idx < tac_srv_no; server_idx++) { + server_fd = tac_connect_single(tac_srv[server_idx].addr, tac_srv[server_idx].key, tac_source_addr, tac_timeout, __vrfname); + if(server_fd < 0) { + // connect to tacacs server failed + output_error("Failed to connecting to %s to request authorization for %s: %s\n", tac_ntop(tac_srv[server_idx].addr->ai_addr), cmd, strerror(errno)); + continue; + } + + // increase connected servers + connected_servers++; + result = send_authorization_message(server_fd, user, tty, host, task_id, cmd, args, argc); + close(server_fd); + if(result) { + // authorization failed + output_debug("%s not authorized from %s\n", cmd, tac_ntop(tac_srv[server_idx].addr->ai_addr)); + } + else { + // authorization successed + output_debug("%s authorized from %s\n", cmd, tac_ntop(tac_srv[server_idx].addr->ai_addr)); + break; + } + } + + // can't connect to any server + if(!connected_servers) { + result = -2; + output_error("Failed to connect to TACACS server(s)\n"); + } + + return result; +} + +/* + * Send authorization request. + * This method based on build_auth_req in https://github.com/daveolson53/tacplus-auth/blob/master/tacplus-auth.c + */ +int authorization_with_host_and_tty(const char *user, const char *cmd, char **argv, int argc) +{ + // try get host name + char hostname[64]; + memset(&hostname, 0, sizeof(hostname)); + + (void)gethostname(hostname, sizeof(hostname) -1); + if (!hostname[0]) { + snprintf(hostname, sizeof(hostname), "UNK"); + output_error("Failed to determine hostname, passing %s\n", hostname); + } + + // try get tty name + char ttyname[64]; + memset(&ttyname, 0, sizeof(ttyname)); + + int i; + for(i=0; i<3; i++) { + int result; + if (isatty(i)) { + result = ttyname_r(i, ttyname, sizeof(ttyname) -1); + if (result) { + output_error("Failed to get tty name for fd %d: %s\n", i, strerror(result)); + } + break; + } + } + + if (!ttyname[0]) { + snprintf(ttyname, sizeof(ttyname), "UNK"); + output_error("Failed to determine tty, passing %s\n", ttyname); + } + + // send tacacs authorization request + return tacacs_authorization(user, ttyname, hostname, cmd, argv, argc); +} + +/* + * Load tacacs config. + */ +void load_tacacs_config() +{ + // load config file: tacacs_config_file + tacacs_ctrl = parse_config_file (tacacs_config_file); + + output_debug("tacacs config updated:\n"); + int server_idx; + for(server_idx = 0; server_idx < tac_srv_no; server_idx++) { + output_debug("Server %d, address:%s, key length:%d\n", server_idx, tac_ntop(tac_srv[server_idx].addr->ai_addr),strlen(tac_srv[server_idx].key)); + } + + output_debug("TACACS+ control flag: 0x%x\n", tacacs_ctrl); + + if (tacacs_ctrl & AUTHORIZATION_FLAG_TACACS) { + output_debug("TACACS+ per-command authorization enabled.\n"); + } + + if (tacacs_ctrl & AUTHORIZATION_FLAG_LOCAL) { + output_debug("Local per-command authorization enabled.\n"); + } + + if (tacacs_ctrl & PAM_TAC_DEBUG) { + output_debug("TACACS+ debug enabled.\n"); + } +} + +/* + * Load tacacs config. + */ +void check_and_load_changed_tacacs_config() +{ + struct stat attr; + // get config file stat, check if file changed + stat(tacacs_config_file, &attr); + char date[CONFIG_FILE_TIME_STAMP_LEN]; + strftime(date, sizeof(date), CONFIG_FILE_TIME_STAMP_FORMAT, localtime(&(attr.st_mtime))); + if (difftime(attr.st_mtime, config_file_attr.st_mtime) == 0) { + output_debug("tacacs config file not change: last modified time: %s.\n", date); + return; + } + + output_debug("tacacs config file changed: last modified time: %s.\n", date); + + // config file changed, update file stat and reload config. + config_file_attr = attr; + + // load config file + load_tacacs_config(); +} + +/* + * Tacacs plugin initialization. + */ +void plugin_init() +{ + // get config file stat, will use this to check config file changed + stat(tacacs_config_file, &config_file_attr); + + // load config file: tacacs_config_file + load_tacacs_config(); + + output_debug("tacacs plugin initialized.\n"); +} + +/* + * Tacacs plugin release. + */ +void plugin_uninit() +{ + output_debug("tacacs plugin un-initialize.\n"); +} + +/* + * Check if current user is local user. + */ +int is_local_user(char *user) +{ + if (user == unknown_username) { + // for unknown user name, when tacacs enabled, always authorization with tacacs. + return IS_REMOTE_USER; + } + + struct passwd pwd; + struct passwd *pwdresult; + char *buf; + size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize == -1) { + bufsize = DEFAULT_SC_GETPW_R_SIZE_MAX; + } + + buf = malloc(bufsize); + if (buf == NULL) { + output_error("failed to allocate getpwnam_r buffer.\n"); + return ERROR_CHECK_LOCAL_USER; + } + + int s = getpwnam_r(user, &pwd, buf, bufsize, &pwdresult); + int result = IS_LOCAL_USER; + if (pwdresult == NULL) { + if (s == 0) + output_error("get user information user failed, user: %s not found\n", user); + else { + output_error("get user information failed, user: %s, errorno: %d\n", user, s); + } + + result = ERROR_CHECK_LOCAL_USER; + } + else if (strncmp(pwd.pw_gecos, REMOTE_USER_GECOS_PREFIX, strlen(REMOTE_USER_GECOS_PREFIX)) == 0) { + output_debug("user: %s, UID: %d, GECOS: %s is remote user.\n", user, pwd.pw_uid, pwd.pw_gecos); + result = IS_REMOTE_USER; + } + else { + output_debug("user: %s, UID: %d, GECOS: %s is local user.\n", user, pwd.pw_uid, pwd.pw_gecos); + result = IS_LOCAL_USER; + } + + free(buf); + return result; +} + +/* + * Get user name. + */ +char* get_user_name(char *user) +{ + if (user != NULL && strlen(user) != 0) { + return user; + } + + // uid is the real user id: https://man7.org/linux/man-pages/man2/geteuid.2.html + output_debug("Login user name is empty, try get user name by euid.\n"); + uid_t uid = getuid(); + struct passwd* userwd = getpwuid(uid); + if (userwd != NULL && userwd->pw_name != NULL) { + return userwd->pw_name; + } + + // euid is the effective user name, may not match real user id: https://man7.org/linux/man-pages/man2/geteuid.2.html + output_debug("Login user name is empty, try get user name by euid.\n"); + uid_t euid = geteuid(); + struct passwd* euserwd = getpwuid(euid); + if (euserwd != NULL && euserwd->pw_name != NULL) { + return euserwd->pw_name; + } + + // if can't find user name by both euid or ruid, return UNKNOWN. + return unknown_username; +} + +/* + * Tacacs authorization. + */ +int on_shell_execve (char *user, int shell_level, char *cmd, char **argv) +{ + char* user_namd = get_user_name(user); + output_debug("Authorization parameters:\n"); + output_debug(" Shell level: %d\n", shell_level); + output_debug(" Current user: %s\n", user_namd); + output_debug(" Command full path: %s\n", cmd); + output_debug(" Parameters:\n"); + char **parameter_array_pointer = argv; + int argc = 0; + while (*parameter_array_pointer != NULL) { + // output parameter + output_debug(" %s\n", *parameter_array_pointer); + + // move to next parameter + parameter_array_pointer++; + argc++; + } + + if (shell_level > 2) { + // when shell_level > 1, it's a recursive command in shell script. + output_debug("Recursive command %s ignored.\n", cmd); + return 0; + } + + // reload config file when tacacs config changed + check_and_load_changed_tacacs_config(); + + int check_local_user_result = is_local_user(user_namd); + if (check_local_user_result != IS_REMOTE_USER) { + /* + Return 0 to check with linux permission control in following 2 scenario: + 1: ERROR_CHECK_LOCAL_USER: check if user is local user failed because can't get user information. + In this case, as failback, check with linux permission control. + 2: IS_LOCAL_USER: user login as local user. + In this case, tacacs authorization disabled for local user. + */ + output_debug("ignore TACACS+ authorization for current user, check with local permission.\n"); + return 0; + } + + if (tacacs_ctrl & AUTHORIZATION_FLAG_TACACS) { + output_debug("start TACACS+ authorization for command %s with given arguments\n", cmd); + int ret = authorization_with_host_and_tty(user_namd, cmd, argv, argc); + switch (ret) { + case 0: + break; + case -2: + // -2 means no servers, so not authorized + fprintf(stdout, "%s not authorized by TACACS+ with given arguments, not executing\n", cmd); + break; + default: + fprintf(stdout, "%s authorize failed by TACACS+ with given arguments, not executing\n", cmd); + break; + } + + if ((tacacs_ctrl & AUTHORIZATION_FLAG_LOCAL) == 0) { + // when local authorization disabled, tacacs authorization failed will block user from run current command + output_debug("local authorization disabled, TACACS+ authorization result: %d\n", ret); + return ret; + } + } + + // return 0, so bash will continue run user command and will check user permission with linux permission check. + output_debug("start local authorization for command %s with given arguments\n", cmd); + return 0; +} \ No newline at end of file diff --git a/src/tacacs/bash_tacplus/configure.ac b/src/tacacs/bash_tacplus/configure.ac new file mode 100644 index 0000000000..fecafc54f1 --- /dev/null +++ b/src/tacacs/bash_tacplus/configure.ac @@ -0,0 +1,71 @@ +dnl +dnl File: configure.in +dnl Revision: $Id: configure.ac,v 1.0 2021/08/24 12:04:29 liuh@microsoft.com Exp $ +dnl Created: 2021/08/24 +dnl Author: Liu Hua +dnl +dnl Process this file with autoconf to produce a configure script +dnl You need autoconf 2.59 or better! +dnl +dnl --------------------------------------------------------------------------- + +AC_PREREQ(2.59) +AC_COPYRIGHT([ +See the included file: COPYING for copyright information. +]) +AC_INIT(bash_tacplus, 1.0.0, [liuh@microsoft.com]) + +AC_CONFIG_AUX_DIR(config) +AM_INIT_AUTOMAKE([foreign]) +AC_CONFIG_SRCDIR([bash_tacplus.c]) +AC_CONFIG_HEADER([config.h]) +AC_CONFIG_MACRO_DIR([config]) + +dnl -------------------------------------------------------------------- +dnl Checks for programs. +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET +AC_ENABLE_SHARED +AC_DISABLE_STATIC +AM_PROG_LIBTOOL + +dnl -------------------------------------------------------------------- +dnl Checks for libraries. +AC_CHECK_LIB(tac, tac_connect) +AC_CHECK_LIB(tacsupport, parse_config_file) + +dnl -------------------------------------------------------------------- +dnl Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/socket.h sys/time.h ]) +AC_CHECK_HEADER([libtac/libtac.h], [], [AC_MSG_ERROR([TAC libraries missing. ])] ) +AC_CHECK_HEADER([libtac/support.h], [], [AC_MSG_ERROR([TAC support libraries missing. ])] ) + +dnl -------------------------------------------------------------------- +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_TYPE_SIZE_T +AC_HEADER_TIME + +dnl -------------------------------------------------------------------- +dnl Checks for library functions. +AC_FUNC_REALLOC +AC_FUNC_SELECT_ARGTYPES +AC_TYPE_SIGNAL +AC_CHECK_FUNCS([bzero gethostbyname gettimeofday inet_ntoa select socket logwtmp getrandom]) + +dnl -------------------------------------------------------------------- +dnl Switch for plugin module dir +AC_ARG_ENABLE([plugindir], [AS_HELP_STRING([--enable-plugindir], + [Location to install the pam module ($libdir/security)])], + [plugindir=$enableval], [plugindir=$libdir/security]) +AC_SUBST(plugindir) + +dnl -------------------------------------------------------------------- +dnl Generate made files +AC_CONFIG_FILES([Makefile + unittest/Makefile]) +AC_OUTPUT diff --git a/src/tacacs/bash_tacplus/debian/bash-tacplus.postinst b/src/tacacs/bash_tacplus/debian/bash-tacplus.postinst new file mode 100644 index 0000000000..85006bb94d --- /dev/null +++ b/src/tacacs/bash_tacplus/debian/bash-tacplus.postinst @@ -0,0 +1,14 @@ +#!/bin/sh +# postinst script for bash-tacplus + +# find installed plugin +bash_tacplus_plugin_path=$(find /usr/lib/ -type f -name "bash_tacplus.so") + +# remove old config from bash plugin config file +config_file_path="/etc/bash_plugins.conf" +if [ -e $config_file_path ]; then + sed -i '/plugin=.*bash_tacplus\.so/d' $config_file_path +fi + +# add new plugin path to plugin config file +echo "plugin="$bash_tacplus_plugin_path >> $config_file_path diff --git a/src/tacacs/bash_tacplus/debian/changelog b/src/tacacs/bash_tacplus/debian/changelog new file mode 100644 index 0000000000..026c8c28fe --- /dev/null +++ b/src/tacacs/bash_tacplus/debian/changelog @@ -0,0 +1,6 @@ +bash-tacplus (1.0.0) unstable; urgency=low + + * First version of bash_tacplus debian package. + + -- Liu Hua Thu, 9 Sep 2021 16:00:00 +0000 + diff --git a/src/tacacs/bash_tacplus/debian/compat b/src/tacacs/bash_tacplus/debian/compat new file mode 100644 index 0000000000..f599e28b8a --- /dev/null +++ b/src/tacacs/bash_tacplus/debian/compat @@ -0,0 +1 @@ +10 diff --git a/src/tacacs/bash_tacplus/debian/control b/src/tacacs/bash_tacplus/debian/control new file mode 100644 index 0000000000..d6325ec6de --- /dev/null +++ b/src/tacacs/bash_tacplus/debian/control @@ -0,0 +1,11 @@ +Source: bash-tacplus +Section: admin +Priority: extra +Maintainer: Liu Hua +Build-Depends: autoconf-archive +Description: Bash TACACS+ plugin. + +Package: bash-tacplus +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libtac2 +Description: Bash TACACS+ plugin for per-command TACACS+ authorization. diff --git a/src/tacacs/bash_tacplus/debian/rules b/src/tacacs/bash_tacplus/debian/rules new file mode 100644 index 0000000000..16f9dd09f5 --- /dev/null +++ b/src/tacacs/bash_tacplus/debian/rules @@ -0,0 +1,27 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +%: + dh $@ + + +override_dh_auto_configure: + dh_auto_configure -- --enable-manuals + +override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info + +override_dh_auto_test: \ No newline at end of file diff --git a/src/tacacs/bash_tacplus/debian/source/format b/src/tacacs/bash_tacplus/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/src/tacacs/bash_tacplus/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/src/tacacs/bash_tacplus/unittest/Makefile.am b/src/tacacs/bash_tacplus/unittest/Makefile.am new file mode 100644 index 0000000000..5f3cf9af19 --- /dev/null +++ b/src/tacacs/bash_tacplus/unittest/Makefile.am @@ -0,0 +1,14 @@ +AUTOMAKE_OPTIONS = subdir-objects + +noinst_PROGRAMS = plugin_test +TESTS = plugin_test + +# disable some warning because UT need test functions not in header file. +CFLAGS_TEST = -Wno-parentheses -Wno-format-security -Wno-implicit-function-declaration -Wno-int-to-pointer-cast +IFLAGS_TEST = -I.. -I../include -I../lib +DBGFLAGS = -DDEBUG -DBASH_PLUGIN_UT + +plugin_test_SOURCES = plugin_test.c mock_helper.c ../bash_tacplus.c + +plugin_test_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_TEST) $(IFLAGS_TEST) +plugin_test_LDADD = -lc -lcunit diff --git a/src/tacacs/bash_tacplus/unittest/mock_helper.c b/src/tacacs/bash_tacplus/unittest/mock_helper.c new file mode 100644 index 0000000000..6edbdbe1ac --- /dev/null +++ b/src/tacacs/bash_tacplus/unittest/mock_helper.c @@ -0,0 +1,209 @@ +/* mock_helper.c -- mock helper for bash plugin UT. */ +#include +#include +#include +#include +#include +#include + +/* Tacacs+ lib */ +#include + +#include "mock_helper.h" + +// define BASH_PLUGIN_UT_DEBUG to output UT debug message. +#if defined (BASH_PLUGIN_UT_DEBUG) +#define debug_printf printf +#define debug_vprintf vprintf +#else +#define debug_printf +#define debug_vprintf +#endif + +/* Mock syslog buffer */ +char mock_syslog_message_buffer[1024]; + +/* define test scenarios for mock functions return different value by scenario. */ +int test_scenario; + +/* Mock tac_netop method result buffer. */ +char tac_natop_result_buffer[128]; + +/* Mock tacplus_server_t. */ +typedef struct { + struct addrinfo *addr; + char key[256]; +} tacplus_server_t; + +/* Mock VRF name. */ +char *__vrfname = "MOCK VRF name"; + +/* Mock tac timeout setting. */ +int tac_timeout = 10; + +/* Mock TACACS servers. */ +int tac_srv_no = 3; +tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS]; +struct addrinfo tac_srv_addr[TAC_PLUS_MAXSERVERS]; +struct sockaddr tac_sock_addr[TAC_PLUS_MAXSERVERS]; + +/* Mock tac_source_addr. */ +struct addrinfo tac_source_addr; + +/* define memory allocate counter. */ +int memory_allocate_count; + +/* Initialize tacacs servers for test*/ +void initialize_tacacs_servers() +{ + for (int idx=0; idx < tac_srv_no; idx++) + { + // generate address with index + struct addrinfo hints, *servers; + char buffer[128]; + snprintf(buffer, sizeof(buffer), "1.2.3.%d", idx); + getaddrinfo(buffer, "49", &hints, &servers); + tac_srv[idx].addr = &(tac_srv_addr[idx]); + memcpy(tac_srv[idx].addr, servers, sizeof(struct addrinfo)); + + tac_srv[idx].addr->ai_addr = &(tac_sock_addr[idx]); + memcpy(tac_srv[idx].addr->ai_addr, servers->ai_addr, sizeof(struct sockaddr)); + + snprintf(tac_srv[idx].key, sizeof(tac_srv[idx].key), "key%d", idx); + freeaddrinfo(servers); + + debug_printf("MOCK: initialize_tacacs_servers with index: %d, address: %p\n", idx, tac_srv[idx].addr); + } +} + +/* Set test scenario for test*/ +void set_test_scenario(int scenario) +{ + test_scenario = scenario; +} + +/* Get test scenario for test*/ +int get_test_scenario() +{ + return test_scenario; +} + +/* Set memory allocate count for test*/ +void set_memory_allocate_count(int count) +{ + memory_allocate_count = count; +} + +/* Get memory allocate count for test*/ +int get_memory_allocate_count() +{ + return memory_allocate_count; +} + +/* Mock xcalloc method */ +void *xcalloc(size_t count, size_t size) +{ + memory_allocate_count++; + debug_printf("MOCK: xcalloc memory count: %d\n", memory_allocate_count); + return malloc(count*size); +} + +/* Mock tac_free_attrib method */ +void tac_add_attrib(struct tac_attrib **attr, char *attrname, char *attrvalue) +{ + debug_printf("MOCK: tac_add_attrib add attribute: %s, value: %s\n", attrname, attrvalue); +} + +/* Mock tac_free_attrib method */ +void tac_free_attrib(struct tac_attrib **attr) +{ + memory_allocate_count--; + debug_printf("MOCK: tac_free_attrib memory count: %d\n", memory_allocate_count); + + // the mock code here only free first allocated memory, because the mock tac_add_attrib implementation not allocate new memory. + free(*attr); +} + +/* Mock tac_author_send method */ +int tac_author_send(int tac_fd, const char *user, char *tty, char *host,struct tac_attrib *attr) +{ + debug_printf("MOCK: tac_author_send with fd: %d, user:%s, tty:%s, host:%s, attr:%p\n", tac_fd, user, tty, host, attr); + if(TEST_SCEANRIO_CONNECTION_SEND_FAILED_RESULT == test_scenario) + { + // send auth message failed + return -1; + } + + return 0; +} + +/* Mock tac_author_read method */ +int tac_author_read(int tac_fd, struct areply *reply) +{ + // TODO: fill reply message here for test + debug_printf("MOCK: tac_author_read with fd: %d\n", tac_fd); + if (TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_READ_FAILED == test_scenario) + { + return -1; + } + + if (TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT == test_scenario) + { + reply->status = AUTHOR_STATUS_FAIL; + } + else + { + reply->status = AUTHOR_STATUS_PASS_REPL; + } + + return 0; +} + +/* Mock tac_connect_single method */ +int tac_connect_single(const struct addrinfo *address, const char *key, struct addrinfo *source_address, int timeout, char *vrfname) +{ + debug_printf("MOCK: tac_connect_single with address: %p\n", address); + + switch (test_scenario) + { + case TEST_SCEANRIO_CONNECTION_ALL_FAILED: + return -1; + } + return 0; +} + +/* Mock tac_ntop method */ +char *tac_ntop(const struct sockaddr *address) +{ + for (int idx=0; idx < tac_srv_no; idx++) + { + if (address == &(tac_sock_addr[idx])) + { + snprintf(tac_natop_result_buffer, sizeof(tac_natop_result_buffer), "TestAddress%d", idx); + return tac_natop_result_buffer; + } + } + + return "UnknownTestAddress"; +} + +/* Mock parse_config_file method */ +int parse_config_file(const char *file) +{ + debug_printf("MOCK: parse_config_file: %s\n", file); +} + +/* Mock syslog method */ +void mock_syslog(int priority, const char *format, ...) +{ + // set mock message data to buffer for UT. + memset(mock_syslog_message_buffer, 0, sizeof(mock_syslog_message_buffer)); + + va_list args; + va_start (args, format); + // save message to buffer to UT check later + vsnprintf(mock_syslog_message_buffer, sizeof(mock_syslog_message_buffer), format, args); + va_end (args); + + debug_printf("MOCK: syslog: %s\n", mock_syslog_message_buffer); +} \ No newline at end of file diff --git a/src/tacacs/bash_tacplus/unittest/mock_helper.h b/src/tacacs/bash_tacplus/unittest/mock_helper.h new file mode 100644 index 0000000000..348b7810fc --- /dev/null +++ b/src/tacacs/bash_tacplus/unittest/mock_helper.h @@ -0,0 +1,46 @@ +/* plugin.h - functions from plugin.c. */ + +/* Copyright (C) 1993-2015 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash 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 3 of the License, or + (at your option) any later version. + + Bash 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 Bash. If not, see . +*/ + +#if !defined (_MOCK_HELPER_H_) +#define _MOCK_HELPER_H_ + +/* Mock syslog buffer */ +extern char mock_syslog_message_buffer[1024]; + +#define TEST_SCEANRIO_CONNECTION_ALL_FAILED 1 +#define TEST_SCEANRIO_CONNECTION_SEND_FAILED_RESULT 2 +#define TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_READ_FAILED 3 +#define TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT 4 +#define TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT 5 + +/* Set test scenario for test*/ +void set_test_scenario(int scenario); + +/* Get test scenario for test*/ +int get_test_scenario(); + +/* Set memory allocate count for test*/ +void set_memory_allocate_count(int count); + +/* Get memory allocate count for test*/ +int get_memory_allocate_count(); + + +#endif /* _MOCK_HELPER_H_ */ \ No newline at end of file diff --git a/src/tacacs/bash_tacplus/unittest/plugin_test.c b/src/tacacs/bash_tacplus/unittest/plugin_test.c new file mode 100644 index 0000000000..2617fd8249 --- /dev/null +++ b/src/tacacs/bash_tacplus/unittest/plugin_test.c @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include "mock_helper.h" +#include + +/* tacacs debug flag */ +extern int tacacs_ctrl; + +int clean_up() { + return 0; +} + +int start_up() { + initialize_tacacs_servers(); + tacacs_ctrl = PAM_TAC_DEBUG; + return 0; +} + +/* Test tacacs_authorization all tacacs server connect failed case */ +void testcase_tacacs_authorization_all_failed() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + + + // test connection failed case + set_test_scenario(TEST_SCEANRIO_CONNECTION_ALL_FAILED); + int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2); + + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "Failed to connect to TACACS server(s)\n"); + + // check return value, -2 for all server not reachable + CU_ASSERT_EQUAL(result, -2); +} + +/* Test tacacs_authorization get failed result case */ +void testcase_tacacs_authorization_faled() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + + // test connection failed case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_FAILED_RESULT); + int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2); + + // send auth message failed. + CU_ASSERT_EQUAL(result, -1); +} + +/* Test tacacs_authorization read failed case */ +void testcase_tacacs_authorization_read_failed() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + + // test connection failed case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_READ_FAILED); + int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2); + + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command not authorized from TestAddress2\n"); + + // read auth message failed. + CU_ASSERT_EQUAL(result, -1); +} + +/* Test tacacs_authorization get denined case */ +void testcase_tacacs_authorization_denined() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + + // test connection denined case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT); + int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2); + + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command not authorized from TestAddress2\n"); + + // send auth message denined. + CU_ASSERT_EQUAL(result, 1); +} + +/* Test tacacs_authorization get success case */ +void testcase_tacacs_authorization_success() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + + // test connection success case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT); + int result = tacacs_authorization("test_user","tty0","test_host","test_command",testargv,2); + + // wuthorization success + CU_ASSERT_EQUAL(result, 0); +} + +/* Test authorization_with_host_and_tty get success case */ +void testcase_authorization_with_host_and_tty_success() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + + // test connection success case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT); + int result = authorization_with_host_and_tty("test_user","test_command",testargv,2); + + // wuthorization success + CU_ASSERT_EQUAL(result, 0); +} + +/* Test check_and_load_changed_tacacs_config */ +void testcase_check_and_load_changed_tacacs_config() { + + // test connection failed case + check_and_load_changed_tacacs_config(); + + // check server config updated. + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "Server 2, address:TestAddress2, key:key2\n"); + + // check and load file again. + check_and_load_changed_tacacs_config(); + + // check server config not update. + char* configNotChangeLog = "tacacs config file not change: last modified time"; + CU_ASSERT_TRUE(strncmp(mock_syslog_message_buffer, configNotChangeLog, strlen(configNotChangeLog)) == 0); +} + +/* Test on_shell_execve authorization successed */ +void testcase_on_shell_execve_success() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + testargv[2] = 0; + + // test connection failed case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_SUCCESS_RESULT); + on_shell_execve("test_user", 1, "test_command", testargv); + + // check authorized success. + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command authorize successed by TACACS+ with given arguments\n"); +} + +/* Test on_shell_execve authorization denined */ +void testcase_on_shell_execve_denined() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + testargv[2] = 0; + + // test connection failed case + set_test_scenario(TEST_SCEANRIO_CONNECTION_SEND_DENINED_RESULT); + on_shell_execve("test_user", 1, "test_command", testargv); + + // check authorized failed. + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command authorize failed by TACACS+ with given arguments, not executing\n"); +} + +/* Test on_shell_execve authorization failed */ +void testcase_on_shell_execve_failed() { + char *testargv[2]; + testargv[0] = "arg1"; + testargv[1] = "arg2"; + testargv[2] = 0; + + // test connection failed case + set_test_scenario(TEST_SCEANRIO_CONNECTION_ALL_FAILED); + on_shell_execve("test_user", 1, "test_command", testargv); + + // check not authorized. + CU_ASSERT_STRING_EQUAL(mock_syslog_message_buffer, "test_command not authorized by TACACS+ with given arguments, not executing\n"); +} + +int main(void) { + if (CUE_SUCCESS != CU_initialize_registry()) { + return CU_get_error(); + } + + CU_pSuite ste = CU_add_suite("plugin_test", start_up, clean_up); + if (NULL == ste) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_get_error() != CUE_SUCCESS) { + fprintf(stderr, "Error creating suite: (%d)%s\n", CU_get_error(), CU_get_error_msg()); + return CU_get_error(); + } + + if (!CU_add_test(ste, "Test testcase_tacacs_authorization_all_failed()...\n", testcase_tacacs_authorization_all_failed) + || !CU_add_test(ste, "Test testcase_tacacs_authorization_faled()...\n", testcase_tacacs_authorization_faled) + || !CU_add_test(ste, "Test testcase_tacacs_authorization_read_failed()...\n", testcase_tacacs_authorization_read_failed) + || !CU_add_test(ste, "Test testcase_tacacs_authorization_denined()...\n", testcase_tacacs_authorization_denined) + || !CU_add_test(ste, "Test testcase_tacacs_authorization_success()...\n", testcase_tacacs_authorization_success) + || !CU_add_test(ste, "Test testcase_authorization_with_host_and_tty_success()...\n", testcase_authorization_with_host_and_tty_success) + || !CU_add_test(ste, "Test testcase_check_and_load_changed_tacacs_config()...\n", testcase_check_and_load_changed_tacacs_config) + || !CU_add_test(ste, "Test testcase_on_shell_execve_success()...\n", testcase_on_shell_execve_success) + || !CU_add_test(ste, "Test testcase_on_shell_execve_denined()...\n", testcase_on_shell_execve_denined) + || !CU_add_test(ste, "Test testcase_on_shell_execve_failed()...\n", testcase_on_shell_execve_failed)) { + CU_cleanup_registry(); + return CU_get_error(); + } + + if (CU_get_error() != CUE_SUCCESS) { + fprintf(stderr, "Error adding test: (%d)%s\n", CU_get_error(), CU_get_error_msg()); + } + + // run all test + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_ErrorCode run_errors = CU_basic_run_suite(ste); + if (run_errors != CUE_SUCCESS) { + fprintf(stderr, "Error running tests: (%d)%s\n", run_errors, CU_get_error_msg()); + } + + CU_basic_show_failures(CU_get_failure_list()); + + // use failed UT count as return value + return CU_get_number_of_failure_records(); +} \ No newline at end of file diff --git a/src/tacacs/pam/0008-Extract-tacacs-support-functions-into-library.patch b/src/tacacs/pam/0008-Extract-tacacs-support-functions-into-library.patch index 139c49564c..60fe2637c1 100644 --- a/src/tacacs/pam/0008-Extract-tacacs-support-functions-into-library.patch +++ b/src/tacacs/pam/0008-Extract-tacacs-support-functions-into-library.patch @@ -1,21 +1,21 @@ -From d820001f60e0a9f5e5df83b1edb229be5212e0b5 Mon Sep 17 00:00:00 2001 +From 81a8b6135cb0c97a291195b04375d0ca33943621 Mon Sep 17 00:00:00 2001 From: liuh-80 <58683130+liuh-80@users.noreply.github.com> Date: Tue, 12 Oct 2021 10:09:10 +0800 -Subject: [PATCH 3/4] Extract tacacs support functions into library. +Subject: [PATCH] Extract tacacs support functions into library. --- Makefile.am | 16 ++- configure.ac | 3 +- libtacsupport.pc.in | 11 ++ pam_tacplus.c | 3 - - pam_tacplus.h | 6 -- - support.c | 255 ++++++++++++++++++++++++++------------------ + pam_tacplus.h | 6 - + support.c | 288 ++++++++++++++++++++++++++++---------------- support.h | 14 +++ - 7 files changed, 194 insertions(+), 114 deletions(-) + 7 files changed, 222 insertions(+), 119 deletions(-) create mode 100644 libtacsupport.pc.in diff --git a/Makefile.am b/Makefile.am -index c90c582..2ac9ea0 100644 +index c90c582..b22c78b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,7 @@ libtac/include/tacplus.h \ @@ -61,7 +61,7 @@ index c90c582..2ac9ea0 100644 +pkgconfig_DATA = libtac.pc libtacsupport.pc diff --git a/configure.ac b/configure.ac -index f67e2ba..0f917a8 100644 +index f67e2ba..e2e3fa9 100644 --- a/configure.ac +++ b/configure.ac @@ -95,6 +95,7 @@ AM_CONDITIONAL(DOC, test "x$enable_doc" != "xno") @@ -75,7 +75,7 @@ index f67e2ba..0f917a8 100644 AC_OUTPUT diff --git a/libtacsupport.pc.in b/libtacsupport.pc.in new file mode 100644 -index 0000000..1f12fe0 +index 0000000..9698094 --- /dev/null +++ b/libtacsupport.pc.in @@ -0,0 +1,11 @@ @@ -122,21 +122,47 @@ index bc71b54..e7b30f7 100644 #define PAM_TAC_VMAJ 1 #define PAM_TAC_VMIN 3 diff --git a/support.c b/support.c -index e22fa31..5b6e1fa 100644 +index 2f77bc8..5f43b1a 100644 --- a/support.c +++ b/support.c -@@ -29,6 +29,7 @@ +@@ -29,7 +29,11 @@ #include #include +#include /* isspace() */ ++/* tacacs config file splitter */ ++#define CONFIG_FILE_SPLITTER " ,\t\n\r\f" ++ /* tacacs server information */ tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS]; -@@ -236,9 +237,160 @@ void set_source_ip(const char *tac_source_ip) { + struct addrinfo tac_srv_addr[TAC_PLUS_MAXSERVERS]; +@@ -234,11 +238,182 @@ void set_source_ip(const char *tac_source_ip) { + freeaddrinfo(source_address); + _pam_log(LOG_DEBUG, "source ip is set"); } - } - ++} ++ ++/* ++ * Reset configuration variables. ++ * This method need to be called before parse config, otherwise the server list will grow with each call. ++ */ ++int reset_config_variables () { ++ memset(tac_srv, 0, sizeof(tacplus_server_t) * TAC_PLUS_MAXSERVERS); ++ tac_srv_no = 0; ++ ++ tac_service[0] = 0; ++ tac_protocol[0] = 0; ++ tac_prompt[0] = 0; ++ tac_login[0] = 0; ++ tac_source_ip[0] = 0; ++ ++ if (tac_source_addr != NULL) { ++ /* reset source address */ ++ tac_source_addr = NULL; ++ } ++} ++ +/* + * Parse one arguments. + * Use this method for both: @@ -254,7 +280,6 @@ index e22fa31..5b6e1fa 100644 + return ctrl; +} /* _pam_parse_arg */ + -+ +/* + * Parse config file. + */ @@ -263,30 +288,31 @@ index e22fa31..5b6e1fa 100644 + char line_buffer[256]; + int ctrl = 0; + ++ /* otherwise the list will grow with each call */ ++ reset_config_variables(); ++ + config_file = fopen(file, "r"); + if(config_file == NULL) { + _pam_log(LOG_ERR, "Failed to open config file %s: %m", file); + return 0; + } + -+ if (tac_source_addr != NULL) { -+ /* reset source address */ -+ tac_source_addr = NULL; -+ } -+ + char current_secret[256]; + memset(current_secret, 0, sizeof(current_secret)); + while (fgets(line_buffer, sizeof line_buffer, config_file)) { + if(*line_buffer == '#' || isspace(*line_buffer)) + continue; /* skip comments and blank line. */ -+ strtok(line_buffer, " \t\n\r\f"); -+ ctrl |= _pam_parse_arg(line_buffer, current_secret, sizeof(current_secret)); ++ char* config_item = strtok(line_buffer, CONFIG_FILE_SPLITTER); ++ while (config_item != NULL) { ++ ctrl |= _pam_parse_arg(config_item, current_secret, sizeof(current_secret)); ++ config_item = strtok(NULL, CONFIG_FILE_SPLITTER); ++ } + } + + fclose(config_file); + return ctrl; -+} -+ + } + int _pam_parse (int argc, const char **argv) { int ctrl = 0; - const char *current_secret = NULL; @@ -295,7 +321,20 @@ index e22fa31..5b6e1fa 100644 /* otherwise the list will grow with each call */ memset(tac_srv, 0, sizeof(tacplus_server_t) * TAC_PLUS_MAXSERVERS); -@@ -256,106 +408,7 @@ int _pam_parse (int argc, const char **argv) { +@@ -248,114 +423,15 @@ int _pam_parse (int argc, const char **argv) { + tac_protocol[0] = 0; + tac_prompt[0] = 0; + tac_login[0] = 0; +- tac_source_ip[0] = 0; +- +- if (tac_source_addr != NULL) { +- /* reset source address */ +- tac_source_addr = NULL; ++ tac_source_ip[0] = 0; ++ ++ if (tac_source_addr != NULL) { ++ /* reset source address */ ++ tac_source_addr = NULL; } for (ctrl = 0; argc-- > 0; ++argv) { @@ -404,7 +443,7 @@ index e22fa31..5b6e1fa 100644 if (ctrl & PAM_TAC_DEBUG) { diff --git a/support.h b/support.h -index 6bcb07f..569172e 100644 +index 6bcb07f..27f66de 100644 --- a/support.h +++ b/support.h @@ -26,6 +26,14 @@