mirror of
				https://github.com/Telecominfraproject/ols-nos.git
				synced 2025-11-01 02:27:58 +00:00 
			
		
		
		
	[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)
			
			
This commit is contained in:
		| @@ -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 |  *   See the License for the specific language governing permissions and | ||||||
|  *   limitations under the License. |  *   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 <pawel.krawczyk@hush.com> and Jeroen Nijhof <jeroen@jeroennijhof.nl>. | ||||||
|  |  *   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 | ||||||
|  |  */ | ||||||
| @@ -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 |     sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f | ||||||
|  |  | ||||||
| # Install customized bash version to patch bash plugin support. | # 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 |     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 | # 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 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 dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-tacplus_*.deb || \ | ||||||
|     sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f |     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 | # Disable tacplus by default | ||||||
| sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove tacplus | sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove tacplus | ||||||
| sudo sed -i -e '/^passwd/s/ tacplus//' $FILESYSTEM_ROOT/etc/nsswitch.conf | sudo sed -i -e '/^passwd/s/ tacplus//' $FILESYSTEM_ROOT/etc/nsswitch.conf | ||||||
|   | |||||||
| @@ -21,3 +21,15 @@ $(LIBNSS_TACPLUS)_CACHE_MODE  := GIT_CONTENT_SHA | |||||||
| $(LIBNSS_TACPLUS)_DEP_FLAGS   := $(SONIC_COMMON_FLAGS_LIST) | $(LIBNSS_TACPLUS)_DEP_FLAGS   := $(SONIC_COMMON_FLAGS_LIST) | ||||||
| $(LIBNSS_TACPLUS)_DEP_FILES   := $(DEP_FILES) | $(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) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,6 +29,19 @@ $(LIBNSS_TACPLUS)_RDEPENDS += $(LIBTAC2) | |||||||
| $(LIBNSS_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/nss | $(LIBNSS_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/nss | ||||||
| SONIC_MAKE_DEBS += $(LIBNSS_TACPLUS) | 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} | # The .c, .cpp, .h & .hpp files under src/{$DBG_SRC_ARCHIVE list} | ||||||
| # are archived into debug one image to facilitate debugging. | # are archived into debug one image to facilitate debugging. | ||||||
| # | # | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								slave.mk
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								slave.mk
									
									
									
									
									
								
							| @@ -943,7 +943,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ | |||||||
|                 $(PYTHON3_SWSSCOMMON) \ |                 $(PYTHON3_SWSSCOMMON) \ | ||||||
|                 $(SONIC_UTILITIES_DATA) \ |                 $(SONIC_UTILITIES_DATA) \ | ||||||
|                 $(SONIC_HOST_SERVICES_DATA) \ |                 $(SONIC_HOST_SERVICES_DATA) \ | ||||||
|                 $(BASH)) \ |                 $(BASH) \ | ||||||
|  |                 $(BASH_TACPLUS)) \ | ||||||
|         $$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \ |         $$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \ | ||||||
|         $$(addprefix $(TARGET_PATH)/,$$(SONIC_PACKAGES_LOCAL)) \ |         $$(addprefix $(TARGET_PATH)/,$$(SONIC_PACKAGES_LOCAL)) \ | ||||||
|         $$(addprefix $(FILES_PATH)/,$$($$*_FILES)) \ |         $$(addprefix $(FILES_PATH)/,$$($$*_FILES)) \ | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/tacacs/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/tacacs/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,6 @@ | |||||||
| * | * | ||||||
| !.gitignore | !.gitignore | ||||||
|  | !bash_tacplus/* | ||||||
| nsm/* | nsm/* | ||||||
| !nsm/Makefile | !nsm/Makefile | ||||||
| !nsm/*.patch | !nsm/*.patch | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								src/tacacs/bash_tacplus/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/tacacs/bash_tacplus/Makefile.am
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										488
									
								
								src/tacacs/bash_tacplus/bash_tacplus.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										488
									
								
								src/tacacs/bash_tacplus/bash_tacplus.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,488 @@ | |||||||
|  | #include <errno.h> | ||||||
|  | #include <limits.h> | ||||||
|  | #include <pwd.h> | ||||||
|  | #include <stdarg.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <syslog.h> | ||||||
|  | #include <sys/stat.h> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <time.h> | ||||||
|  | #include <unistd.h> | ||||||
|  |  | ||||||
|  | /* 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 <libtac/libtac.h> | ||||||
|  |  | ||||||
|  | /* Tacacs+ support lib */ | ||||||
|  | #include <libtac/support.h> | ||||||
|  |  | ||||||
|  | /* 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<argc; i++) { | ||||||
|  |         // TACACS protocol allow max 255 bytes per argument. 'cmd-arg' will take 7 bytes. | ||||||
|  |         char tbuf[248]; | ||||||
|  |         const char *arg; | ||||||
|  |         if(strlen(args[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; | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								src/tacacs/bash_tacplus/configure.ac
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/tacacs/bash_tacplus/configure.ac
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <liuh@microsoft.com> | ||||||
|  | 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 | ||||||
							
								
								
									
										14
									
								
								src/tacacs/bash_tacplus/debian/bash-tacplus.postinst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/tacacs/bash_tacplus/debian/bash-tacplus.postinst
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||||
							
								
								
									
										6
									
								
								src/tacacs/bash_tacplus/debian/changelog
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/tacacs/bash_tacplus/debian/changelog
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | bash-tacplus (1.0.0) unstable; urgency=low | ||||||
|  |  | ||||||
|  |   * First version of bash_tacplus debian package. | ||||||
|  |  | ||||||
|  |  -- Liu Hua <liuh@microsoft.com>  Thu, 9 Sep 2021 16:00:00 +0000 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								src/tacacs/bash_tacplus/debian/compat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/tacacs/bash_tacplus/debian/compat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 10 | ||||||
							
								
								
									
										11
									
								
								src/tacacs/bash_tacplus/debian/control
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tacacs/bash_tacplus/debian/control
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | Source: bash-tacplus | ||||||
|  | Section: admin | ||||||
|  | Priority: extra | ||||||
|  | Maintainer: Liu Hua <liuh@microsoft.com> | ||||||
|  | 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. | ||||||
							
								
								
									
										27
									
								
								src/tacacs/bash_tacplus/debian/rules
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/tacacs/bash_tacplus/debian/rules
									
									
									
									
									
										Normal file
									
								
							| @@ -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: | ||||||
							
								
								
									
										1
									
								
								src/tacacs/bash_tacplus/debian/source/format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/tacacs/bash_tacplus/debian/source/format
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | 3.0 (quilt) | ||||||
							
								
								
									
										14
									
								
								src/tacacs/bash_tacplus/unittest/Makefile.am
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/tacacs/bash_tacplus/unittest/Makefile.am
									
									
									
									
									
										Normal file
									
								
							| @@ -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  | ||||||
							
								
								
									
										209
									
								
								src/tacacs/bash_tacplus/unittest/mock_helper.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/tacacs/bash_tacplus/unittest/mock_helper.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | /* mock_helper.c -- mock helper for bash plugin UT. */ | ||||||
|  | #include <stdarg.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <CUnit/CUnit.h> | ||||||
|  | #include <CUnit/Basic.h> | ||||||
|  |  | ||||||
|  | /* Tacacs+ lib */ | ||||||
|  | #include <libtac/libtac.h> | ||||||
|  |  | ||||||
|  | #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); | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/tacacs/bash_tacplus/unittest/mock_helper.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/tacacs/bash_tacplus/unittest/mock_helper.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | #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_ */ | ||||||
							
								
								
									
										219
									
								
								src/tacacs/bash_tacplus/unittest/plugin_test.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/tacacs/bash_tacplus/unittest/plugin_test.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | |||||||
|  | #include <stdio.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <CUnit/CUnit.h> | ||||||
|  | #include <CUnit/Basic.h> | ||||||
|  | #include "mock_helper.h" | ||||||
|  | #include <libtac/support.h> | ||||||
|  |  | ||||||
|  | /* 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(); | ||||||
|  | } | ||||||
| @@ -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> | From: liuh-80 <58683130+liuh-80@users.noreply.github.com> | ||||||
| Date: Tue, 12 Oct 2021 10:09:10 +0800 | 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 ++- |  Makefile.am         |  16 ++- | ||||||
|  configure.ac        |   3 +- |  configure.ac        |   3 +- | ||||||
|  libtacsupport.pc.in |  11 ++ |  libtacsupport.pc.in |  11 ++ | ||||||
|  pam_tacplus.c       |   3 - |  pam_tacplus.c       |   3 - | ||||||
|  pam_tacplus.h       |   6 -- |  pam_tacplus.h       |   6 - | ||||||
|  support.c           | 255 ++++++++++++++++++++++++++------------------ |  support.c           | 288 ++++++++++++++++++++++++++++---------------- | ||||||
|  support.h           |  14 +++ |  support.h           |  14 +++ | ||||||
|  7 files changed, 194 insertions(+), 114 deletions(-) |  7 files changed, 222 insertions(+), 119 deletions(-) | ||||||
|  create mode 100644 libtacsupport.pc.in |  create mode 100644 libtacsupport.pc.in | ||||||
|  |  | ||||||
| diff --git a/Makefile.am b/Makefile.am | diff --git a/Makefile.am b/Makefile.am | ||||||
| index c90c582..2ac9ea0 100644 | index c90c582..b22c78b 100644 | ||||||
| --- a/Makefile.am | --- a/Makefile.am | ||||||
| +++ b/Makefile.am | +++ b/Makefile.am | ||||||
| @@ -20,7 +20,7 @@ libtac/include/tacplus.h \ | @@ -20,7 +20,7 @@ libtac/include/tacplus.h \ | ||||||
| @@ -61,7 +61,7 @@ index c90c582..2ac9ea0 100644 | |||||||
| +pkgconfig_DATA = libtac.pc libtacsupport.pc  | +pkgconfig_DATA = libtac.pc libtacsupport.pc  | ||||||
|   |   | ||||||
| diff --git a/configure.ac b/configure.ac | diff --git a/configure.ac b/configure.ac | ||||||
| index f67e2ba..0f917a8 100644 | index f67e2ba..e2e3fa9 100644 | ||||||
| --- a/configure.ac | --- a/configure.ac | ||||||
| +++ b/configure.ac | +++ b/configure.ac | ||||||
| @@ -95,6 +95,7 @@ AM_CONDITIONAL(DOC, test "x$enable_doc" != "xno") | @@ -95,6 +95,7 @@ AM_CONDITIONAL(DOC, test "x$enable_doc" != "xno") | ||||||
| @@ -75,7 +75,7 @@ index f67e2ba..0f917a8 100644 | |||||||
|  AC_OUTPUT |  AC_OUTPUT | ||||||
| diff --git a/libtacsupport.pc.in b/libtacsupport.pc.in | diff --git a/libtacsupport.pc.in b/libtacsupport.pc.in | ||||||
| new file mode 100644 | new file mode 100644 | ||||||
| index 0000000..1f12fe0 | index 0000000..9698094 | ||||||
| --- /dev/null | --- /dev/null | ||||||
| +++ b/libtacsupport.pc.in | +++ b/libtacsupport.pc.in | ||||||
| @@ -0,0 +1,11 @@ | @@ -0,0 +1,11 @@ | ||||||
| @@ -122,21 +122,47 @@ index bc71b54..e7b30f7 100644 | |||||||
|  #define PAM_TAC_VMAJ 1 |  #define PAM_TAC_VMAJ 1 | ||||||
|  #define PAM_TAC_VMIN 3 |  #define PAM_TAC_VMIN 3 | ||||||
| diff --git a/support.c b/support.c | diff --git a/support.c b/support.c | ||||||
| index e22fa31..5b6e1fa 100644 | index 2f77bc8..5f43b1a 100644 | ||||||
| --- a/support.c | --- a/support.c | ||||||
| +++ b/support.c | +++ b/support.c | ||||||
| @@ -29,6 +29,7 @@ | @@ -29,7 +29,11 @@ | ||||||
|   |   | ||||||
|  #include <stdlib.h> |  #include <stdlib.h> | ||||||
|  #include <string.h> |  #include <string.h> | ||||||
| +#include <ctype.h> /* isspace() */ | +#include <ctype.h> /* isspace() */ | ||||||
|   |   | ||||||
|  | +/* tacacs config file splitter */ | ||||||
|  | +#define CONFIG_FILE_SPLITTER " ,\t\n\r\f" | ||||||
|  | + | ||||||
|  /* tacacs server information */ |  /* tacacs server information */ | ||||||
|  tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS]; |  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. | + * Parse one arguments. | ||||||
| + * Use this method for both: | + * Use this method for both: | ||||||
| @@ -254,7 +280,6 @@ index e22fa31..5b6e1fa 100644 | |||||||
| +    return ctrl; | +    return ctrl; | ||||||
| +}    /* _pam_parse_arg */ | +}    /* _pam_parse_arg */ | ||||||
| + | + | ||||||
| + |  | ||||||
| +/* | +/* | ||||||
| + * Parse config file. | + * Parse config file. | ||||||
| + */ | + */ | ||||||
| @@ -263,30 +288,31 @@ index e22fa31..5b6e1fa 100644 | |||||||
| +    char line_buffer[256]; | +    char line_buffer[256]; | ||||||
| +    int ctrl = 0; | +    int ctrl = 0; | ||||||
| + | + | ||||||
|  | +    /* otherwise the list will grow with each call */ | ||||||
|  | +    reset_config_variables(); | ||||||
|  | + | ||||||
| +    config_file = fopen(file, "r"); | +    config_file = fopen(file, "r"); | ||||||
| +    if(config_file == NULL) { | +    if(config_file == NULL) { | ||||||
| +        _pam_log(LOG_ERR, "Failed to open config file %s: %m", file); | +        _pam_log(LOG_ERR, "Failed to open config file %s: %m", file); | ||||||
| +        return 0; | +        return 0; | ||||||
| +    } | +    } | ||||||
| + | + | ||||||
| +    if (tac_source_addr != NULL) { |  | ||||||
| +        /* reset source address */ |  | ||||||
| +        tac_source_addr = NULL; |  | ||||||
| +    } |  | ||||||
| + |  | ||||||
| +    char current_secret[256]; | +    char current_secret[256]; | ||||||
| +    memset(current_secret, 0, sizeof(current_secret)); | +    memset(current_secret, 0, sizeof(current_secret)); | ||||||
| +    while (fgets(line_buffer, sizeof line_buffer, config_file)) { | +    while (fgets(line_buffer, sizeof line_buffer, config_file)) { | ||||||
| +        if(*line_buffer == '#' || isspace(*line_buffer)) | +        if(*line_buffer == '#' || isspace(*line_buffer)) | ||||||
| +            continue; /* skip comments and blank line. */ | +            continue; /* skip comments and blank line. */ | ||||||
| +        strtok(line_buffer, " \t\n\r\f"); | +        char* config_item = strtok(line_buffer, CONFIG_FILE_SPLITTER); | ||||||
| +        ctrl |= _pam_parse_arg(line_buffer, current_secret, sizeof(current_secret)); | +        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); | +    fclose(config_file); | ||||||
| +    return ctrl; | +    return ctrl; | ||||||
| +} |  } | ||||||
| + |   | ||||||
|  int _pam_parse (int argc, const char **argv) { |  int _pam_parse (int argc, const char **argv) { | ||||||
|      int ctrl = 0; |      int ctrl = 0; | ||||||
| -    const char *current_secret = NULL; | -    const char *current_secret = NULL; | ||||||
| @@ -295,7 +321,20 @@ index e22fa31..5b6e1fa 100644 | |||||||
|   |   | ||||||
|      /* otherwise the list will grow with each call */ |      /* otherwise the list will grow with each call */ | ||||||
|      memset(tac_srv, 0, sizeof(tacplus_server_t) * TAC_PLUS_MAXSERVERS); |      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) { |      for (ctrl = 0; argc-- > 0; ++argv) { | ||||||
| @@ -404,7 +443,7 @@ index e22fa31..5b6e1fa 100644 | |||||||
|   |   | ||||||
|      if (ctrl & PAM_TAC_DEBUG) { |      if (ctrl & PAM_TAC_DEBUG) { | ||||||
| diff --git a/support.h b/support.h | diff --git a/support.h b/support.h | ||||||
| index 6bcb07f..569172e 100644 | index 6bcb07f..27f66de 100644 | ||||||
| --- a/support.h | --- a/support.h | ||||||
| +++ b/support.h | +++ b/support.h | ||||||
| @@ -26,6 +26,14 @@ | @@ -26,6 +26,14 @@ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 liuh-80
					liuh-80