diff --git a/build_debian.sh b/build_debian.sh index e609a0878..0c744585f 100755 --- a/build_debian.sh +++ b/build_debian.sh @@ -324,7 +324,8 @@ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y in haveged \ fdisk \ gpg \ - jq + jq \ + auditd if [[ $CONFIGURED_ARCH == amd64 ]]; then ## Pre-install the fundamental packages for amd64 (x86) diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index 349a87bcd..6cd852e6e 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -277,6 +277,9 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-tacplus_*.deb || \ # 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 +# Install audisp-tacplus +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/audisp-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.mk b/rules/tacacs.mk index f8796e6ff..06c2c497a 100644 --- a/rules/tacacs.mk +++ b/rules/tacacs.mk @@ -16,8 +16,6 @@ LIBTAC_DEV = libtac-dev_$(PAM_TACPLUS_VERSION)_$(CONFIGURED_ARCH).deb $(LIBTAC_DEV)_DEPENDS += $(LIBTAC2) $(eval $(call add_derived_package,$(LIBTAC2),$(LIBTAC_DEV))) - - # libnss-tacplus packages NSS_TACPLUS_VERSION = 1.0.4-1 @@ -29,6 +27,16 @@ $(LIBNSS_TACPLUS)_RDEPENDS += $(LIBTAC2) $(LIBNSS_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/nss SONIC_MAKE_DEBS += $(LIBNSS_TACPLUS) +# audisp-tacplus packages +AUDISP_TACPLUS_VERSION = 1.0.2 + +export AUDISP_TACPLUS_VERSION + +AUDISP_TACPLUS = audisp-tacplus_$(AUDISP_TACPLUS_VERSION)_$(CONFIGURED_ARCH).deb +$(AUDISP_TACPLUS)_DEPENDS += $(LIBTAC_DEV) +$(AUDISP_TACPLUS)_RDEPENDS += $(LIBTAC2) +$(AUDISP_TACPLUS)_SRC_PATH = $(SRC_PATH)/tacacs/audisp +SONIC_MAKE_DEBS += $(AUDISP_TACPLUS) # bash-tacplus packages BASH_TACPLUS_VERSION = 1.0.0 @@ -41,7 +49,6 @@ $(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 830d6fd8c..d729f8ef1 100644 --- a/slave.mk +++ b/slave.mk @@ -949,7 +949,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(SONIC_UTILITIES_DATA) \ $(SONIC_HOST_SERVICES_DATA) \ $(BASH) \ - $(BASH_TACPLUS)) \ + $(BASH_TACPLUS) \ + $(AUDISP_TACPLUS)) \ $$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \ $$(addprefix $(TARGET_PATH)/,$$(SONIC_PACKAGES_LOCAL)) \ $$(addprefix $(FILES_PATH)/,$$($$*_FILES)) \ diff --git a/sonic-slave-bullseye/Dockerfile.j2 b/sonic-slave-bullseye/Dockerfile.j2 index 8ed4446f0..8ef31c040 100644 --- a/sonic-slave-bullseye/Dockerfile.j2 +++ b/sonic-slave-bullseye/Dockerfile.j2 @@ -349,7 +349,10 @@ RUN apt-get update && apt-get install -y \ libdbus-1-dev \ libgirepository1.0-dev \ libsystemd-dev \ - pkg-config + pkg-config \ +# For audisp-tacplus + libauparse-dev \ + auditd RUN apt-get -y build-dep openssh diff --git a/sonic-slave-buster/Dockerfile.j2 b/sonic-slave-buster/Dockerfile.j2 index 9f59f0c0e..144f3f03f 100644 --- a/sonic-slave-buster/Dockerfile.j2 +++ b/sonic-slave-buster/Dockerfile.j2 @@ -354,7 +354,10 @@ RUN apt-get update && apt-get install -y \ libdbus-1-dev \ libgirepository1.0-dev \ libsystemd-dev \ - pkg-config + pkg-config \ +# For audisp-tacplus + libauparse-dev \ + auditd # For iproute2 RUN apt-get install -y -t buster-backports \ diff --git a/sonic-slave-jessie/Dockerfile.j2 b/sonic-slave-jessie/Dockerfile.j2 index b23f06880..67eb1fbfd 100644 --- a/sonic-slave-jessie/Dockerfile.j2 +++ b/sonic-slave-jessie/Dockerfile.j2 @@ -239,6 +239,9 @@ RUN apt-get update && apt-get install -y \ libcunit1-dev \ # For initramfs bash-completion \ +# For audisp-tacplus + libauparse-dev \ + auditd \ {% if CONFIGURED_ARCH == "amd64" -%} # For sonic vs image build dosfstools \ diff --git a/sonic-slave-stretch/Dockerfile.j2 b/sonic-slave-stretch/Dockerfile.j2 index 8417021ff..af1795347 100644 --- a/sonic-slave-stretch/Dockerfile.j2 +++ b/sonic-slave-stretch/Dockerfile.j2 @@ -295,7 +295,10 @@ RUN apt-get update && apt-get install -y \ libxml2-utils \ xsltproc \ python-lxml \ - libexpat1-dev + libexpat1-dev \ +# For audisp-tacplus + libauparse-dev \ + auditd # Install dependencies for dhcp relay test RUN pip3 install parameterized==0.8.1 diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index a914dbed0..9b39fb5eb 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -100,6 +100,18 @@ def obfuscate(data): else: return data +def get_pid(procname): + for dirname in os.listdir('/proc'): + if dirname == 'curproc': + continue + try: + with open('/proc/{}/cmdline'.format(dirname), mode='r') as fd: + content = fd.read() + except Exception as ex: + continue + if procname in content: + return dirname + return "" class Feature(object): """ Represents a feature configuration from CONFIG_DB data. """ @@ -534,6 +546,18 @@ class AaaCfg(object): if modify_conf: self.modify_conf_file() + def notify_audisp_tacplus_reload_config(self): + pid = get_pid("/sbin/audisp-tacplus") + syslog.syslog(syslog.LOG_INFO, "Found audisp-tacplus PID: {}".format(pid)) + if pid == "": + return + + # audisp-tacplus will reload TACACS+ config when receive SIGHUP + try: + os.kill(int(pid), signal.SIGHUP) + except Exception as ex: + syslog.syslog(syslog.LOG_WARNING, "Send SIGHUP to audisp-tacplus failed with exception: {}".format(ex)) + def handle_radius_source_intf_ip_chg(self, key): modify_conf=False if 'src_intf' in self.radius_global: @@ -769,6 +793,9 @@ class AaaCfg(object): with open(NSS_TACPLUS_CONF, 'w') as f: f.write(nss_tacplus_conf) + # Notify auditd plugin to reload tacacs config. + self.notify_audisp_tacplus_reload_config() + # Set debug in nss-radius conf template_file = os.path.abspath(NSS_RADIUS_CONF_TEMPLATE) template = env.get_template(template_file) diff --git a/src/tacacs/.gitignore b/src/tacacs/.gitignore index 48b82082a..c0a967a92 100644 --- a/src/tacacs/.gitignore +++ b/src/tacacs/.gitignore @@ -1,5 +1,8 @@ * !.gitignore +audisp/* +!audisp/Makefile +!audisp/*.patch !bash_tacplus/* nsm/* !nsm/Makefile diff --git a/src/tacacs/audisp/Makefile b/src/tacacs/audisp/Makefile new file mode 100644 index 000000000..6bf80cc67 --- /dev/null +++ b/src/tacacs/audisp/Makefile @@ -0,0 +1,30 @@ +.ONESHELL: +SHELL = /bin/bash +.SHELLFLAGS += -e + +MAIN_TARGET = audisp-tacplus_$(AUDISP_TACPLUS_VERSION)_$(CONFIGURED_ARCH).deb + +$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : + # Obtain audisp-tacplus + rm -rf ./audisp-tacplus + + git clone https://github.com/daveolson53/audisp-tacplus.git + + # checkout by sha1 + pushd ./audisp-tacplus + git checkout 559c9f22edd4f2dea0ecedffb3ad9502b12a75b6 + + # Apply patches + cp -r ../patches patches + quilt push -a + + # fix aclocal depency issue by run auto.sh + ./auto.sh + + # build package + dpkg-buildpackage -rfakeroot -b -us -uc -j$(SONIC_CONFIG_MAKE_JOBS) --admindir $(SONIC_DPKG_ADMINDIR) + popd + + mv $(DERIVED_TARGETS) $* $(DEST)/ + +$(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET) \ No newline at end of file diff --git a/src/tacacs/audisp/patches/0001-Porting-to-sonic.patch b/src/tacacs/audisp/patches/0001-Porting-to-sonic.patch new file mode 100644 index 000000000..343b14b3e --- /dev/null +++ b/src/tacacs/audisp/patches/0001-Porting-to-sonic.patch @@ -0,0 +1,426 @@ +From 3a552cb456ebc233ef55970509a58a5c378acd7b Mon Sep 17 00:00:00 2001 +From: liuh-80 <58683130+liuh-80@users.noreply.github.com> +Date: Tue, 9 Nov 2021 16:34:12 +0800 +Subject: [PATCH 1/3] Porting to sonic. + +Fixed issue in this patch: +1. The upstream project using 'libtacplus-map' library to lookup_logname, remove that depency by implement by ourself because libtacplus-map not porting to sonic, also because the libnss-tacplus in sonic not using libtacplus-map for user name mapping, sonic using a different solution for user login handling. + +2. The libpam-tacplus been changed to support 'source address', which add new parameter to tacacs functions. + +3. Upstream project using a patched version of libpam_tacplus, so some method in that not exist in sonic version. + +4. For tacacs config file parse and load, code change using the shared method in tacsupport lib. +--- + Makefile.am | 3 +- + Makefile.in | 3 +- + audisp-tacplus.c | 234 +++++++++++++------------------------------- + audisp-tacplus.conf | 2 +- + debian/control | 6 +- + 5 files changed, 76 insertions(+), 172 deletions(-) + +diff --git a/Makefile.am b/Makefile.am +index ad70ca0..caead49 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -6,7 +6,7 @@ EXTRA_DIST = ChangeLog README audisp_tacplus.spec \ + + audisp_tacplus_SOURCES = audisp-tacplus.c + audisp_tacplus_CFLAGS = -O +-audisp_tacplus_LDADD = -lauparse -ltacplus_map ++audisp_tacplus_LDADD = -lauparse -ltacsupport -ltac + sbin_PROGRAMS = audisp-tacplus + man_MANS = audisp-tacplus.8 + +@@ -27,7 +27,6 @@ install-data-hook: + ${INSTALL} -m 755 audisp-tacplus $(DESTDIR)$(sbindir) + ${INSTALL} -d $(DESTDIR)$(sysconfdir)/audisp/plugins.d + ${INSTALL} -d $(DESTDIR)$(sysconfdir)/audit/rules.d +- ${INSTALL} -m 600 audisp-tac_plus.conf $(DESTDIR)$(sysconfdir)/audisp/ + ${INSTALL} -m 644 audisp-tacplus.conf $(DESTDIR)$(sysconfdir)/audisp/plugins.d + ${INSTALL} -m 644 -o 0 audisp-tacplus.rules $(DESTDIR)$(sysconfdir)/audit/rules.d + +diff --git a/Makefile.in b/Makefile.in +index d124c16..5482a9a 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -345,8 +345,7 @@ target_alias = @target_alias@ + top_build_prefix = @top_build_prefix@ + top_builddir = @top_builddir@ + top_srcdir = @top_srcdir@ +-EXTRA_DIST = ChangeLog README audisp_tacplus.spec \ +- audisp-tac_plus.conf audisp-tacplus.conf ++EXTRA_DIST = ChangeLog README audisp_tacplus.spec audisp-tacplus.conf + + audisp_tacplus_SOURCES = audisp-tacplus.c + audisp_tacplus_CFLAGS = -O +diff --git a/audisp-tacplus.c b/audisp-tacplus.c +index 42841e5..5e3fb63 100644 +--- a/audisp-tacplus.c ++++ b/audisp-tacplus.c +@@ -45,8 +45,10 @@ + + #include + #include ++#include + #include + #include ++#include + #include + #include + #include +@@ -54,10 +56,14 @@ + #include + #include + #include ++#include ++#include + ++#include ++#include + +-#include +-#include ++/* Tacacs+ support lib */ ++#include + + #define _VMAJ 1 + #define _VMIN 0 +@@ -71,6 +77,9 @@ static unsigned connected_ok; + + char *configfile = "/etc/audisp/audisp-tac_plus.conf"; + ++/* Tacacs control flag */ ++int tacacs_ctrl; ++ + /* Local declarations */ + static void handle_event(auparse_state_t *au, + auparse_cb_event_t cb_event_type, void *user_data); +@@ -93,179 +102,74 @@ hup_handler(int sig __attribute__ ((unused))) + hup = 1; + } + +-typedef struct { +- struct addrinfo *addr; +- char *key; +-} tacplus_server_t; +- +-/* set from configuration file parsing */ +-static tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS]; +-static int tac_srv_no, tac_key_no; +-static char tac_service[64]; +-static char tac_protocol[64]; +-static char vrfname[64]; +-static int debug = 0; +-static int acct_all; /* send accounting to all servers, not just 1st */ +- + static const char *progname = "audisp-tacplus"; /* for syslogs and errors */ + + static void +-audisp_tacplus_config(char *cfile, int level) ++reload_config(void) + { +- FILE *conf; +- char lbuf[256]; +- +- conf = fopen(cfile, "r"); +- if(conf == NULL) { +- syslog(LOG_WARNING, "%s: can't open config file %s: %m", +- progname, cfile); +- return; +- } ++ hup = 0; + +- while(fgets(lbuf, sizeof lbuf, conf)) { +- if(*lbuf == '#' || isspace(*lbuf)) +- continue; /* skip comments, white space lines, etc. */ +- strtok(lbuf, " \t\n\r\f"); /* terminate buffer at first whitespace */ +- if(!strncmp(lbuf, "include=", 8)) { +- /* +- * allow include files, useful for centralizing tacacs +- * server IP address and secret. +- */ +- if(lbuf[8]) /* else treat as empty config */ +- audisp_tacplus_config(&lbuf[8], level+1); +- } +- else if(!strncmp(lbuf, "debug=", 6)) +- debug = strtoul(lbuf+6, NULL, 0); +- else if(!strncmp(lbuf, "acct_all=", 9)) +- acct_all = strtoul(lbuf+9, NULL, 0); +- else if(!strncmp(lbuf, "vrf=", 4)) +- tac_xstrcpy(vrfname, lbuf + 4, sizeof(vrfname)); +- else if(!strncmp(lbuf, "service=", 8)) +- tac_xstrcpy(tac_service, lbuf + 8, sizeof(tac_service)); +- else if(!strncmp(lbuf, "protocol=", 9)) +- tac_xstrcpy(tac_protocol, lbuf + 9, sizeof(tac_protocol)); +- else if(!strncmp(lbuf, "login=", 6)) +- tac_xstrcpy(tac_login, lbuf + 6, sizeof(tac_login)); +- else if (!strncmp (lbuf, "timeout=", 8)) { +- tac_timeout = (int)strtoul(lbuf+8, NULL, 0); +- if (tac_timeout < 0) /* explict neg values disable poll() use */ +- tac_timeout = 0; +- else /* poll() only used if timeout is explictly set */ +- tac_readtimeout_enable = 1; +- } +- else if(!strncmp(lbuf, "secret=", 7)) { +- int i; +- /* no need to complain if too many on this one */ +- if(tac_key_no < TAC_PLUS_MAXSERVERS) { +- if((tac_srv[tac_key_no].key = strdup(lbuf+7))) +- tac_key_no++; +- else +- syslog(LOG_ERR, "%s: unable to copy server secret %s", +- __FUNCTION__, lbuf+7); +- } +- /* handle case where 'secret=' was given after a 'server=' +- * parameter, fill in the current secret */ +- for(i = tac_srv_no-1; i >= 0; i--) { +- if (tac_srv[i].key) +- continue; +- tac_srv[i].key = strdup(lbuf+7); +- } +- } +- else if(!strncmp(lbuf, "server=", 7)) { +- if(tac_srv_no < TAC_PLUS_MAXSERVERS) { +- struct addrinfo hints, *servers, *server; +- int rv; +- char *port, server_buf[sizeof lbuf]; +- +- memset(&hints, 0, sizeof hints); +- hints.ai_family = AF_UNSPEC; /* use IPv4 or IPv6, whichever */ +- hints.ai_socktype = SOCK_STREAM; +- +- strcpy(server_buf, lbuf + 7); +- +- port = strchr(server_buf, ':'); +- if(port != NULL) { +- *port = '\0'; +- port++; +- } +- if((rv = getaddrinfo(server_buf, (port == NULL) ? +- "49" : port, &hints, &servers)) == 0) { +- for(server = servers; server != NULL && +- tac_srv_no < TAC_PLUS_MAXSERVERS; +- server = server->ai_next) { +- tac_srv[tac_srv_no].addr = server; +- /* use current key, if our index not yet set */ +- if(tac_key_no && !tac_srv[tac_srv_no].key) +- tac_srv[tac_srv_no].key = +- strdup(tac_srv[tac_key_no-1].key); +- tac_srv_no++; +- } +- } +- else { +- syslog(LOG_ERR, +- "skip invalid server: %s (getaddrinfo: %s)", +- server_buf, gai_strerror(rv)); +- } +- } +- else { +- syslog(LOG_ERR, "maximum number of servers (%d) exceeded, " +- "skipping", TAC_PLUS_MAXSERVERS); +- } +- } +- else if(debug) /* ignore unrecognized lines, unless debug on */ +- syslog(LOG_WARNING, "%s: unrecognized parameter: %s", +- progname, lbuf); +- } ++ connected_ok = 0; /* reset connected state (for possible vrf) */ + +- if(level == 0 && (!tac_service[0] || tac_srv_no == 0)) +- syslog(LOG_ERR, "%s version %d.%d.%d: missing tacacs fields in file %s, %d servers", +- progname, _VMAJ, _VMIN, _VPATCH, configfile, tac_srv_no); ++ /* load config file: configfile */ ++ tacacs_ctrl = parse_config_file(configfile); + +- if(debug) { +- int n; +- syslog(LOG_NOTICE, "%s version %d.%d.%d tacacs service=%s", progname, +- _VMAJ, _VMIN, _VPATCH, tac_service); ++ trace("tacacs config updated:\n"); ++ int server_idx; ++ for(server_idx = 0; server_idx < tac_srv_no; server_idx++) { ++ trace("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)); ++ } + +- for(n = 0; n < tac_srv_no; n++) +- syslog(LOG_DEBUG, "%s: tacacs server[%d] { addr=%s, key='%s' }", +- progname, n, tac_ntop(tac_srv[n].addr->ai_addr), +- tac_srv[n].key); ++ trace("TACACS+ control flag: 0x%x\n", tacacs_ctrl); ++ ++ if (tacacs_ctrl & AUTHORIZATION_FLAG_TACACS) { ++ trace("TACACS+ per-command authorization enabled.\n"); + } + +- fclose(conf); ++ if (tacacs_ctrl & AUTHORIZATION_FLAG_LOCAL) { ++ trace("Local per-command authorization enabled.\n"); ++ } ++ ++ if (tacacs_ctrl & PAM_TAC_DEBUG) { ++ trace("TACACS+ debug enabled.\n"); ++ } + } + +- +-static void +-reload_config(void) ++/* ++ * Get user name by UID, and return NULL when not found user name by UID. ++ * The returned username should be free by caller. ++ * Also assign hostname to host, the host also should be free by caller. ++ */ ++char *lookup_logname(uid_t auid, char** host) + { +- int i, nservers; +- +- hup = 0; ++ /* get user name. */ ++ struct passwd *pws; ++ pws = getpwuid(auid); ++ if (pws == NULL) { ++ /* Failed to get user information. */ ++ return NULL; ++ } + +- /* reset the config variables that we use, freeing memory where needed */ +- nservers = tac_srv_no; +- tac_srv_no = 0; +- tac_key_no = 0; +- vrfname[0] = '\0'; +- tac_service[0] = '\0'; +- tac_protocol[0] = '\0'; +- tac_login[0] = '\0'; +- debug = 0; +- acct_all = 0; +- tac_timeout = 0; +- +- for(i = 0; i < nservers; i++) { +- if(tac_srv[i].key) { +- free(tac_srv[i].key); +- tac_srv[i].key = NULL; +- } +- tac_srv[i].addr = NULL; ++ int new_buffer_size = strlen(pws->pw_name) + 1; ++ char* username = malloc(new_buffer_size); ++ if (username == NULL) { ++ /* Failed to allocate new buffer. */ ++ return NULL; + } + +- connected_ok = 0; /* reset connected state (for possible vrf) */ ++ memset(username, 0, new_buffer_size); ++ strncpy(username, pws->pw_name, new_buffer_size-1); ++ ++ /* get hostname. */ ++ *host = malloc(HOST_NAME_MAX+1); ++ memset(host, 0, HOST_NAME_MAX+1); ++ if (gethostname(host, HOST_NAME_MAX) != 0) ++ { ++ free(*host); ++ *host = NULL; ++ } + +- audisp_tacplus_config(configfile, 0); ++ return username; + } + + int +@@ -273,6 +177,9 @@ main(int argc, char *argv[]) + { + char tmp[MAX_AUDIT_MESSAGE_LENGTH+1]; + struct sigaction sa; ++ ++ /* initialize random seed*/ ++ srand(time(NULL)); + + /* if there is an argument, it is an alternate configuration file */ + if(argc > 1) +@@ -334,7 +241,7 @@ send_acct_msg(int tac_fd, int type, char *user, char *tty, char *host, + int retval; + struct areply re; + +- attr=(struct tac_attrib *)tac_xcalloc(1, sizeof(struct tac_attrib)); ++ attr=(struct tac_attrib *)xcalloc(1, sizeof(struct tac_attrib)); + + snprintf(buf, sizeof buf, "%lu", (unsigned long)time(NULL)); + tac_add_attrib(&attr, "start_time", buf); +@@ -378,8 +285,7 @@ send_tacacs_acct(char *user, char *tty, char *host, char *cmdmsg, int type, + int retval, srv_i, srv_fd; + + for(srv_i = 0; srv_i < tac_srv_no; srv_i++) { +- srv_fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key, +- NULL, vrfname[0]?vrfname:NULL); ++ srv_fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key, tac_source_addr, tac_timeout, __vrfname); + if(srv_fd < 0) { + syslog(LOG_WARNING, "connection to %s failed (%d) to send" + " accounting record: %m", +@@ -393,7 +299,7 @@ send_tacacs_acct(char *user, char *tty, char *host, char *cmdmsg, int type, + close(srv_fd); + if(!retval) { + connected_ok = 1; +- if(!acct_all) ++ if(!(tacacs_ctrl & PAM_TAC_ACCT)) + break; /* only send to first responding server */ + } + } +@@ -501,7 +407,7 @@ static void get_acct_record(auparse_state_t *au, int type) + taskno = (uint16_t) pid; + } + else /* should never happen, if it does, records won't match */ +- taskno = tac_magic(); ++ taskno = (u_int32_t)rand(); + + if(get_field(au, "auid")) { + auser = (char *)auparse_interpret_field(au); +@@ -520,7 +426,7 @@ static void get_acct_record(auparse_state_t *au, int type) + * the NSS library, the username in auser will likely already be the login + * name. + */ +- loguser = lookup_logname(NULL, auid, session, &host, NULL); ++ loguser = lookup_logname(auid, &host); + if(!loguser) { + char *user = NULL; + +diff --git a/audisp-tacplus.conf b/audisp-tacplus.conf +index ccb8517..ba77880 100644 +--- a/audisp-tacplus.conf ++++ b/audisp-tacplus.conf +@@ -7,7 +7,7 @@ active = yes + + # This can be used to specify an different config file than + # /etc/audisp/audisp-tac_plus.conf if desired +-# args = ++args=/etc/tacplus_nss.conf + + # These parameters should normally not be changed. + direction = out +diff --git a/debian/control b/debian/control +index 5306bd9..28c7324 100644 +--- a/debian/control ++++ b/debian/control +@@ -2,9 +2,9 @@ Source: audisp-tacplus + Section: admin + Priority: optional + Maintainer: Dave Olson +-Build-Depends: debhelper (>= 9), autotools-dev, autoconf, libpam-tacplus-dev, ++Build-Depends: debhelper (>= 9), autotools-dev, autoconf, + libtac-dev (>= 1.4.1~), +- libaudit-dev, libauparse-dev, libtacplus-map-dev, ++ libaudit-dev, libauparse-dev, + dpkg-dev (>= 1.16.1~), git + Standards-Version: 3.9.6 + Homepage: https://github.com/daveolson53/audisp_tacplus +@@ -12,7 +12,7 @@ Homepage: https://github.com/daveolson53/audisp_tacplus + Package: audisp-tacplus + Architecture: any + Depends: ${shlibs:Depends}, ${misc:Depends}, libpam-tacplus, libtac2 (>= 1.4.1~), +- libtacplus-map1, libauparse0, libaudit1, auditd ++ libauparse0, libaudit1, auditd + Description: audisp module for TACACS+ accounting + This audisp module is an audisp auditd plugin. It is designed to do TACACS+ + accounting for commands run by TACACS+ authenticated sessions. +-- +2.17.1.windows.2 + diff --git a/src/tacacs/audisp/patches/0002-Remove-user-secret-from-accounting-log.patch b/src/tacacs/audisp/patches/0002-Remove-user-secret-from-accounting-log.patch new file mode 100644 index 000000000..1cacaa8a0 --- /dev/null +++ b/src/tacacs/audisp/patches/0002-Remove-user-secret-from-accounting-log.patch @@ -0,0 +1,1139 @@ +From 4152d4d91e1e333ff30f028b3e17e8669ef01bf3 Mon Sep 17 00:00:00 2001 +From: liuh-80 <58683130+liuh-80@users.noreply.github.com> +Date: Tue, 23 Nov 2021 13:32:51 +0800 +Subject: [PATCH] Remove user secret from accounting log. + +--- + Makefile.am | 2 +- + audisp-tacplus.c | 19 ++- + audisp-tacplus.conf | 2 +- + password.c | 118 ++++++++++++++++++ + password.h | 31 +++++ + regex_helper.c | 92 ++++++++++++++ + regex_helper.h | 17 +++ + sudoers_helper.c | 250 +++++++++++++++++++++++++++++++++++++++ + sudoers_helper.h | 18 +++ + trace.c | 21 ++++ + trace.h | 10 ++ + unittest/Makefile | 21 ++++ + unittest/mock.h | 17 +++ + unittest/mock_helper.c | 65 ++++++++++ + unittest/mock_helper.h | 48 ++++++++ + unittest/password_test.c | 199 +++++++++++++++++++++++++++++++ + unittest/sudoers | 5 + + 17 files changed, 931 insertions(+), 4 deletions(-) + create mode 100644 password.c + create mode 100644 password.h + create mode 100644 regex_helper.c + create mode 100644 regex_helper.h + create mode 100644 sudoers_helper.c + create mode 100644 sudoers_helper.h + create mode 100644 trace.c + create mode 100644 trace.h + create mode 100644 unittest/Makefile + create mode 100644 unittest/mock.h + create mode 100644 unittest/mock_helper.c + create mode 100644 unittest/mock_helper.h + create mode 100644 unittest/password_test.c + create mode 100644 unittest/sudoers + +diff --git a/Makefile.am b/Makefile.am +index caead49..b6cb92b 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -4,7 +4,7 @@ + EXTRA_DIST = ChangeLog README audisp_tacplus.spec \ + audisp-tac_plus.conf audisp-tacplus.conf + +-audisp_tacplus_SOURCES = audisp-tacplus.c ++audisp_tacplus_SOURCES = audisp-tacplus.c password.c regex_helper.c trace.c sudoers_helper.c + audisp_tacplus_CFLAGS = -O + audisp_tacplus_LDADD = -lauparse -ltacsupport -ltac + sbin_PROGRAMS = audisp-tacplus +diff --git a/audisp-tacplus.c b/audisp-tacplus.c +index 5e3fb63..aa2b16f 100644 +--- a/audisp-tacplus.c ++++ b/audisp-tacplus.c +@@ -65,6 +65,10 @@ + /* Tacacs+ support lib */ + #include + ++/* Remove password from user command */ ++#include "password.h" ++#include "sudoers_helper.h" ++ + #define _VMAJ 1 + #define _VMIN 0 + #define _VPATCH 0 +@@ -80,6 +84,9 @@ char *configfile = "/etc/audisp/audisp-tac_plus.conf"; + /* Tacacs control flag */ + int tacacs_ctrl; + ++/* sudoers file conatins user password setting */ ++const char *sudoers_path = "/etc/sudoers"; ++ + /* Local declarations */ + static void handle_event(auparse_state_t *au, + auparse_cb_event_t cb_event_type, void *user_data); +@@ -162,8 +169,8 @@ char *lookup_logname(uid_t auid, char** host) + + /* get hostname. */ + *host = malloc(HOST_NAME_MAX+1); +- memset(host, 0, HOST_NAME_MAX+1); +- if (gethostname(host, HOST_NAME_MAX) != 0) ++ memset(*host, 0, HOST_NAME_MAX+1); ++ if (gethostname(*host, HOST_NAME_MAX) != 0) + { + free(*host); + *host = NULL; +@@ -201,6 +208,10 @@ main(int argc, char *argv[]) + syslog(LOG_ERR, "exitting due to auparse init errors"); + return -1; + } ++ ++ /* initialize password regex setting */ ++ initialize_password_setting(sudoers_path); ++ + auparse_add_callback(au, handle_event, NULL, NULL); + do { + /* Load configuration */ +@@ -229,6 +240,9 @@ main(int argc, char *argv[]) + auparse_flush_feed(au); + auparse_destroy(au); + ++ /* Release password setting */ ++ release_password_setting(); ++ + return 0; + } + +@@ -511,6 +525,7 @@ static void get_acct_record(auparse_state_t *au, int type) + * loguser is always set, we bail if not. For ANOM_ABEND, tty may be + * unknown, and in some cases, host may be not be set. + */ ++ remove_password(logbase); + send_tacacs_acct(loguser, tty?tty:"UNK", host?host:"UNK", logbase, acct_type, taskno); + + if(host) +diff --git a/audisp-tacplus.conf b/audisp-tacplus.conf +index ba77880..1b46a9c 100644 +--- a/audisp-tacplus.conf ++++ b/audisp-tacplus.conf +@@ -7,7 +7,7 @@ active = yes + + # This can be used to specify an different config file than + # /etc/audisp/audisp-tac_plus.conf if desired +-args=/etc/tacplus_nss.conf ++args = /etc/tacplus_nss.conf + + # These parameters should normally not be changed. + direction = out +diff --git a/password.c b/password.c +new file mode 100644 +index 0000000..8484d39 +--- /dev/null ++++ b/password.c +@@ -0,0 +1,118 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "password.h" ++#include "regex_helper.h" ++#include "sudoers_helper.h" ++#include "trace.h" ++ ++/* Regex list */ ++REGEX_NODE *global_regex_list = NULL; ++ ++/* Append regex to list */ ++int append_regex_to_list(regex_t regex) ++{ ++ /* Create and initialize regex node */ ++ REGEX_NODE *new_regex_node = (REGEX_NODE *)malloc(sizeof(REGEX_NODE)); ++ if (new_regex_node == NULL) ++ { ++ /* When allocate memory failed, stop and return. also output log to both syslog and stderr with LOG_PERROR*/ ++ trace("Failed to allocate memory for regex node.\n"); ++ return REGEX_APPEND_FAILED; ++ } ++ ++ new_regex_node->next = NULL; ++ new_regex_node->regex = regex; ++ ++ /* Find the pointer to the latest regex node's 'next' field */ ++ REGEX_NODE **current_node = &global_regex_list; ++ while (*current_node != NULL) { ++ current_node = &((*current_node)->next); ++ } ++ ++ /* append new regex to tail node */ ++ *current_node = new_regex_node; ++ return REGEX_APPEND_SUCCESS; ++} ++ ++/* Release password setting */ ++void release_password_setting() ++{ ++ if (global_regex_list == NULL) { ++ return; ++ } ++ ++ /* Walk to last regex */ ++ REGEX_NODE *current = global_regex_list; ++ while (current != NULL) { ++ /* Continue with next regex */ ++ REGEX_NODE* current_node_memory = current; ++ current = current->next; ++ ++ /* Free node memory, this may also reset all allocated memory depends on c lib implementation */ ++ free(current_node_memory); ++ } ++ ++ /* Reset list */ ++ global_regex_list = NULL; ++} ++ ++/* Replace password with PASSWORD_MASK by regex. */ ++void remove_password(char* command) ++{ ++ if (global_regex_list == NULL) { ++ return; ++ } ++ ++ /* Check every regex */ ++ REGEX_NODE *next_node = global_regex_list; ++ while (next_node != NULL) { ++ /* Try fix password with current regex */ ++ if (remove_password_by_regex(command, next_node->regex) == PASSWORD_REMOVED) { ++ return; ++ } ++ ++ /* If password not fix, continue try next regex */ ++ next_node = next_node->next; ++ } ++} ++ ++/* Find and return the pointer of the first non-space character*/ ++char* find_non_space(char *str) ++{ ++ if (str == NULL) { ++ return str; ++ } ++ ++ while (isspace(*str)) { ++ str++; ++ } ++ ++ return str; ++} ++ ++/* Append passwd_cmd to global list */ ++int append_password_regex(char *passwd_cmd) ++{ ++ trace("Append passwd_cmd: %s\n", passwd_cmd); ++ ++ /* convert the setting string to regex */ ++ char regex_buffer[MAX_LINE_SIZE+1]; ++ passwd_cmd = find_non_space(passwd_cmd); ++ convert_passwd_cmd_to_regex(regex_buffer, sizeof(regex_buffer), passwd_cmd); ++ ++ regex_t regex; ++ if (regcomp(®ex, regex_buffer, REG_NEWLINE)) { ++ trace("Complie regex failed: %s\n", regex_buffer); ++ return INITIALIZE_INCORRECT_REGEX; ++ } ++ ++ /* Append regex to global list */ ++ append_regex_to_list(regex); ++ ++ return INITIALIZE_SUCCESS; ++} +\ No newline at end of file +diff --git a/password.h b/password.h +new file mode 100644 +index 0000000..2563b31 +--- /dev/null ++++ b/password.h +@@ -0,0 +1,31 @@ ++#ifndef USER_SECRED_H ++#define USER_SECRED_H ++ ++#include ++#include ++ ++/* Macros for initialize result */ ++#define INITIALIZE_SUCCESS 0 ++#define INITIALIZE_LOAD_SETTING_FAILED 1 ++#define INITIALIZE_INCORRECT_REGEX 2 ++ ++/* Regex append result. */ ++#define REGEX_APPEND_SUCCESS 0 ++#define REGEX_APPEND_FAILED 1 ++ ++/* Regex list node. */ ++typedef struct regex_node { ++ struct regex_node *next; ++ regex_t regex; ++} REGEX_NODE; ++ ++/* Release password setting */ ++extern void release_password_setting(); ++ ++/* Replace password with regex */ ++extern void remove_password(char* command); ++ ++/* Append passwd_cmd to global list */ ++int append_password_regex(char *passwd_cmd); ++ ++#endif /* USER_SECRED_H */ +\ No newline at end of file +diff --git a/regex_helper.c b/regex_helper.c +new file mode 100644 +index 0000000..1edea94 +--- /dev/null ++++ b/regex_helper.c +@@ -0,0 +1,92 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "regex_helper.h" ++#include "trace.h" ++ ++#define min(a,b) (((a) < (b)) ? (a) : (b)) ++ ++/* ++ * Macros for password regex ++ * These are BRE regex, please refer to: https://en.wikibooks.org/wiki/Regular_Expressions/POSIX_Basic_Regular_Expressions ++ REGEX_WHITESPACES will match the whitespace in user commands. ++ REGEX_TOKEN will match password or connection string in user commands ++ */ ++#define REGEX_WHITESPACES "[[:space:]]*" ++#define REGEX_TOKEN "\\([^[:space:]]*\\)" ++ ++/* Regex match group count, 2 because only have 1 subexpression for password */ ++#define REGEX_MATCH_GROUP_COUNT 2 ++ ++/* The password mask */ ++#define PASSWORD_MASK '*' ++ ++/* Remove password from command. */ ++int remove_password_by_regex(char* command, regex_t regex) ++{ ++ regmatch_t pmatch[REGEX_MATCH_GROUP_COUNT]; ++ if (regexec(®ex, command, REGEX_MATCH_GROUP_COUNT, pmatch, 0) == REG_NOMATCH) { ++ trace("User command not match.\n"); ++ return PASSWORD_NOT_FOUND; ++ } ++ ++ if (pmatch[1].rm_so < 0) { ++ trace("Password not found.\n"); ++ return PASSWORD_NOT_FOUND; ++ } ++ ++ /* Found password between pmatch[1].rm_so to pmatch[1].rm_eo, replace it. */ ++ trace("Found password between: %d -- %d\n", pmatch[1].rm_so, pmatch[1].rm_eo); ++ ++ /* Replace password with mask. */ ++ size_t command_length = strlen(command); ++ int password_start_pos = min(pmatch[1].rm_so, command_length); ++ int password_count = min(pmatch[1].rm_eo, command_length) - password_start_pos; ++ memset(command + password_start_pos, PASSWORD_MASK, password_count); ++ ++ return PASSWORD_REMOVED; ++} ++ ++/* ++ Convert password command to regex. ++ Password commands defined in sudoers file, the PASSWD_CMD alias is a list of password command. ++ For more information please check: ++ https://www.sudo.ws/man/1.7.10/sudoers.man.html ++ https://github.com/Azure/sonic-buildimage/blob/5c503b81ae186aa378928edf36fa1d347e919d7a/files/image_config/sudoers/sudoers ++ */ ++void convert_passwd_cmd_to_regex(char *buf, size_t buf_size, const char* password_setting) ++{ ++ int src_idx = 0; ++ int last_char_is_whitespace = 0; ++ ++ memset(buf, 0, buf_size); ++ while (password_setting[src_idx]) { ++ int buffer_used_space= strlen(buf); ++ if (password_setting[src_idx] == PASSWORD_MASK) { ++ /* Replace * to REGEX_TOKEN */ ++ snprintf(buf + buffer_used_space, buf_size - buffer_used_space, REGEX_TOKEN); ++ } ++ else if (isspace(password_setting[src_idx])) { ++ /* Ignore mutiple whitespace */ ++ if (!last_char_is_whitespace) { ++ /* Replace whitespace to regex REGEX_WHITESPACES which match multiple whitespace */ ++ snprintf(buf + buffer_used_space, buf_size - buffer_used_space, REGEX_WHITESPACES); ++ } ++ } ++ else if (buffer_used_space < buf_size - 1) { ++ /* Copy none password characters */ ++ buf[buffer_used_space] = password_setting[src_idx]; ++ } ++ else { ++ /* Buffer full, return here. */ ++ return; ++ } ++ ++ last_char_is_whitespace = isspace(password_setting[src_idx]); ++ src_idx++; ++ } ++} +\ No newline at end of file +diff --git a/regex_helper.h b/regex_helper.h +new file mode 100644 +index 0000000..33c1916 +--- /dev/null ++++ b/regex_helper.h +@@ -0,0 +1,17 @@ ++#ifndef REGEX_HELPER_H ++#define REGEX_HELPER_H ++ ++#include ++#include ++ ++/* Regex fix result. */ ++#define PASSWORD_REMOVED 0 ++#define PASSWORD_NOT_FOUND 1 ++ ++/* Remove password from command. */ ++extern int remove_password_by_regex(char* command, regex_t regex); ++ ++/* Convert password setting to regex. */ ++extern void convert_passwd_cmd_to_regex(char *buf, size_t buf_size, const char* password_setting); ++ ++#endif /* REGEX_HELPER_H */ +\ No newline at end of file +diff --git a/sudoers_helper.c b/sudoers_helper.c +new file mode 100644 +index 0000000..ac4191e +--- /dev/null ++++ b/sudoers_helper.c +@@ -0,0 +1,250 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "password.h" ++#include "sudoers_helper.h" ++#include "trace.h" ++ ++/* Macros for parse user input */ ++#define SUDOERS_SETTING_SPLITTER " =\t\n" ++#define SUDOERS_EQUAL "=" ++#define SUDOERS_LF '\n' ++ ++#define PASSWD_CMDS_SPLITTER '\r' ++ ++/* The command alias prefix */ ++const char* COMMAND_ALIAS = "Cmnd_Alias"; ++ ++/* The password setting */ ++const char* PASSWD_CMDS = "PASSWD_CMDS"; ++ ++/* ++ Load file content. ++*/ ++char* load_file_content(const char *setting_path) ++{ ++ FILE *setting_file = fopen(setting_path, "rt"); ++ if(setting_file == NULL) { ++ trace("Can't open setting file: %s\n", setting_path); ++ return NULL; ++ } ++ ++ fseek(setting_file, 0, SEEK_END); ++ size_t setting_file_size = ftell(setting_file); ++ fseek(setting_file, 0, SEEK_SET); ++ ++ char* file_content = malloc(setting_file_size+1); ++ if (file_content == NULL) { ++ trace("Allocate memory for file: %s failed.\n", setting_path); ++ } ++ else { ++ size_t result = fread(file_content, sizeof(char), setting_file_size, setting_file); ++ if (result == setting_file_size) { ++ file_content[setting_file_size] = 0; ++ } ++ else { ++ trace("Read setting file: %s failed.\n", setting_path); ++ free(file_content); ++ file_content = NULL; ++ } ++ } ++ ++ fclose(setting_file); ++ return file_content; ++} ++ ++ ++/* ++ Get setting content length ++*/ ++size_t setting_content_length(const char *setting) ++{ ++ size_t length = 0; ++ while (*setting != 0 && *setting != SUDOERS_LF) { ++ length++; ++ setting++; ++ } ++ ++ return length; ++} ++ ++/* ++ Load PASSWD_CMDS from sudoers. ++ For more information please check: ++ https://www.sudo.ws/man/1.8.17/sudoers.man.html#Other_special_characters_and_reserved_words ++*/ ++char* load_passwd_cmds(const char *setting_path) ++{ ++ char* file_content = load_file_content(setting_path); ++ if(file_content == NULL) { ++ trace("Load file: %s failed.\n", setting_path); ++ return NULL; ++ } ++ ++ escape_characters(file_content); ++ trace("Sudoers content: (%s)\n", file_content); ++ ++ char *passwd_cmds = NULL; ++ char* token = strtok(file_content, SUDOERS_SETTING_SPLITTER); ++ while (token != NULL) { ++ trace("Token: (%s)\n", token); ++ /* Find Cmnd_Alias */ ++ if (strncmp(token, COMMAND_ALIAS, strlen(COMMAND_ALIAS))) { ++ token = strtok(NULL, SUDOERS_SETTING_SPLITTER); ++ continue; ++ } ++ ++ /* Find PASSWD_CMDS setting */ ++ token = strtok(NULL, SUDOERS_SETTING_SPLITTER); ++ if (strncmp(token, PASSWD_CMDS, strlen(PASSWD_CMDS))) { ++ token = strtok(NULL, SUDOERS_SETTING_SPLITTER); ++ continue; ++ } ++ ++ /* Get PASSWD_CMDS setting content */ ++ token = strtok(NULL, SUDOERS_EQUAL); ++ size_t setting_length = setting_content_length(token); ++ passwd_cmds = malloc(setting_length+1); ++ if (passwd_cmds == NULL) { ++ trace("Allocate memory for PASSWD_CMDS buffer failed.\n"); ++ break; ++ } ++ ++ memcpy(passwd_cmds, token, setting_length); ++ passwd_cmds[setting_length] = 0; ++ break; ++ } ++ ++ free(file_content); ++ return passwd_cmds; ++} ++ ++/* ++ Escape characters according to sudoers file format. ++ For more information, please check: ++ The following characters must be escaped with a backslash (‘\’) when used as part of a word (e.g. a user name or host name): ‘!’, ‘=’, ‘:’, ‘,’, ‘(’, ‘)’, ‘\’. ++ https://www.sudo.ws/man/1.8.17/sudoers.man.html#Other_special_characters_and_reserved_words ++*/ ++void escape_characters(char *str) ++{ ++ char *src_pos=str; ++ char *dest_pos=str; ++ while (*src_pos) { ++ if (*src_pos == ',') { ++ /* PASSWD_CMDS use comma as splitter, replace it wiith \n to simplify split handling */ ++ *dest_pos = PASSWD_CMDS_SPLITTER; ++ src_pos++; ++ dest_pos++; ++ continue; ++ } ++ else if (*src_pos != '\\') { ++ /* copy none escape characters */ ++ if (dest_pos != src_pos) { ++ *dest_pos = *src_pos; ++ } ++ ++ src_pos++; ++ dest_pos++; ++ continue; ++ } ++ ++ /* Handle escape characters */ ++ src_pos++; ++ switch (*src_pos) ++ { ++ case '!': ++ case '=': ++ case '"': ++ case ',': ++ case '(': ++ case ')': ++ case '\\': ++ *dest_pos = *src_pos; ++ dest_pos++; ++ src_pos++; ++ continue; ++ case '\n': ++ /* Long lines can be continued with a backslash */ ++ src_pos++; ++ continue; ++ } ++ ++ /* Not a escape character */ ++ *dest_pos = '\\'; ++ dest_pos++; ++ ++ *dest_pos = *src_pos; ++ src_pos++; ++ dest_pos++; ++ } ++ ++ *dest_pos = 0; ++} ++ ++/* ++ Initialize password setting from sudoers. ++*/ ++int initialize_password_setting(const char *setting_path) ++{ ++ char* passwd_cmds = load_passwd_cmds(setting_path); ++ if (passwd_cmds == NULL) { ++ /* Setting file open failed or can't find password setting. */ ++ trace("Load PASSWD_CMDS from: %s failed.\n", setting_path); ++ return INITIALIZE_LOAD_SETTING_FAILED; ++ } ++ ++ trace("Loaded PASSWD_CMDS: (%s), from: %s .\n", passwd_cmds, setting_path); ++ ++ /* Split PASSWD_CMDS with comma */ ++ int result = INITIALIZE_SUCCESS; ++ int passwd_cmds_length = strlen(passwd_cmds); ++ char* passwd_cmd = passwd_cmds; ++ bool start_new_passwd_cmd = true; ++ for (int index=0; index < passwd_cmds_length; index++) { ++ if (start_new_passwd_cmd) { ++ /* ++ Set the passwd_cmd point to new command when: ++ 1. beginning of passwd_cmds. ++ 2. After a comma splitter. ++ */ ++ passwd_cmd = passwd_cmds + index; ++ start_new_passwd_cmd = false; ++ } ++ ++ if (passwd_cmds[index] != PASSWD_CMDS_SPLITTER) { ++ continue; ++ } ++ ++ /* Found a splitter, handle current passwd_cmd. */ ++ passwd_cmds[index] = 0; ++ result = append_password_regex(passwd_cmd); ++ if (result != INITIALIZE_SUCCESS) { ++ trace("Append password regex failed: %s, result: %d\n", passwd_cmd, result); ++ break; ++ } ++ ++ /* ++ Set passwd_cmd to NULL, so multiple comma splitter will not create empty passwd_cmd, for example: ++ command1,,command2 ++ */ ++ passwd_cmd = NULL; ++ start_new_passwd_cmd = true; ++ } ++ ++ /* ++ Handle following 2 cases: ++ 1. Comma splitter not exist in PASSWD_CMDS ++ 2. Last command in PASSWD_CMDS ++ */ ++ result = append_password_regex(passwd_cmd); ++ if (result != INITIALIZE_SUCCESS) { ++ trace("Append password regex failed: %s, result: %d\n", passwd_cmd, result); ++ } ++ ++ free(passwd_cmds); ++ return result; ++} +\ No newline at end of file +diff --git a/sudoers_helper.h b/sudoers_helper.h +new file mode 100644 +index 0000000..1539821 +--- /dev/null ++++ b/sudoers_helper.h +@@ -0,0 +1,18 @@ ++#ifndef SUDOERS_HELPER_H ++#define SUDOERS_HELPER_H ++ ++/* Load PASSWD_CMDS from sudoers. */ ++char* load_passwd_cmds(const char *setting_path); ++ ++/* ++ Escape characters according to sudoers file format. ++ For more information, please check: ++ The following characters must be escaped with a backslash (‘\’) when used as part of a word (e.g. a user name or host name): ‘!’, ‘=’, ‘:’, ‘,’, ‘(’, ‘)’, ‘\’. ++ https://www.sudo.ws/man/1.8.17/sudoers.man.html#Other_special_characters_and_reserved_words ++*/ ++void escape_characters(char *str); ++ ++/* Initialize password setting from sudoers. */ ++int initialize_password_setting(const char *setting_path); ++ ++#endif /* SUDOERS_HELPER_H */ +diff --git a/trace.c b/trace.c +new file mode 100644 +index 0000000..44bbbc7 +--- /dev/null ++++ b/trace.c +@@ -0,0 +1,21 @@ ++#include ++#include ++#include ++#include ++#include ++ ++#include "trace.h" ++ ++/* Output trace log. */ ++void trace(const char *format, ...) ++{ ++ // convert log to a string because va args resoursive issue: ++ // http://www.c-faq.com/varargs/handoff.html ++ char logBuffer[MAX_LINE_SIZE]; ++ va_list args; ++ va_start (args, format); ++ vsnprintf(logBuffer, sizeof(logBuffer), format, args); ++ va_end (args); ++ ++ syslog(LOG_INFO, "Audisp-tacplus: %s", logBuffer); ++} +\ No newline at end of file +diff --git a/trace.h b/trace.h +new file mode 100644 +index 0000000..6ea929d +--- /dev/null ++++ b/trace.h +@@ -0,0 +1,10 @@ ++#ifndef TRACE_H ++#define TRACE_H ++ ++/* Max setting line buffer size */ ++#define MAX_LINE_SIZE 512 ++ ++/* Output trace log. */ ++extern void trace(const char *format, ...); ++ ++#endif /* USER_SECRED_H */ +\ No newline at end of file +diff --git a/unittest/Makefile b/unittest/Makefile +new file mode 100644 +index 0000000..ed5517b +--- /dev/null ++++ b/unittest/Makefile +@@ -0,0 +1,21 @@ ++#disable some warning because UT need test functions not in header file. ++CFLAGS = -Wno-parentheses -Wno-format-security -Wno-implicit-function-declaration -c ++IFLAGS = -I.. -I../include -I../lib -include mock.h ++MFLAG = -DDEBUG -DUNIT_TEST ++ ++all: ++ gcc password_test.c $(IFLAGS) $(CFLAGS) -o password_test.o ++ gcc mock_helper.c $(IFLAGS) $(CFLAGS) -o mock_helper.o ++ gcc ../password.c $(IFLAGS) $(CFLAGS) $(MFLAG) -o password.o ++ gcc ../regex_helper.c $(IFLAGS) $(CFLAGS) $(MFLAG) -o regex_helper.o ++ gcc ../trace.c $(IFLAGS) $(CFLAGS) $(MFLAG) -o trace.o ++ gcc ../sudoers_helper.c $(IFLAGS) $(CFLAGS) $(MFLAG) -o sudoers_helper.o ++ gcc password_test.o mock_helper.o password.o regex_helper.o trace.o sudoers_helper.o -o password_test -lc -lcunit ++ ++test: ++ # run unit test, if UT failed, build will break ++ ./password_test ++ ++clean: ++ rm *.o ++ rm password_test +diff --git a/unittest/mock.h b/unittest/mock.h +new file mode 100644 +index 0000000..536f17a +--- /dev/null ++++ b/unittest/mock.h +@@ -0,0 +1,17 @@ ++#ifndef MOCK_H ++#define MOCK_H ++ ++#include ++ ++// use mock functions when build for UT ++#if defined (UNIT_TEST) ++void *mock_malloc(size_t size); ++void *mock_realloc(void* ptr, size_t size); ++void mock_free(void* ptr); ++#define malloc mock_malloc ++#define realloc mock_realloc ++#define free mock_free ++#else ++#endif ++ ++#endif /* MOCK_H */ +diff --git a/unittest/mock_helper.c b/unittest/mock_helper.c +new file mode 100644 +index 0000000..cd6433d +--- /dev/null ++++ b/unittest/mock_helper.c +@@ -0,0 +1,65 @@ ++/* mock_helper.c -- mock helper for bash plugin UT. */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include "mock_helper.h" ++ ++/* define test scenarios for mock functions return different value by scenario. */ ++int test_scenario; ++ ++/* define memory allocate counter. */ ++int memory_allocate_count; ++ ++/* 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 malloc method*/ ++void *mock_malloc(size_t size) ++{ ++ memory_allocate_count++; ++ debug_printf("MOCK: malloc %ld bytes memory count: %d\n", size, memory_allocate_count); ++ return malloc(size); ++} ++ ++/* MOCK malloc method*/ ++void *mock_realloc(void* ptr, size_t size) ++{ ++ if (ptr == NULL) { ++ memory_allocate_count++; ++ } ++ ++ debug_printf("MOCK: realloc %ld bytes memory count: %d\n", size, memory_allocate_count); ++ return realloc(ptr, size); ++} ++ ++/* MOCK free method*/ ++void mock_free(void* ptr) ++{ ++ memory_allocate_count--; ++ debug_printf("MOCK: free memory count: %d\n", memory_allocate_count); ++ free(ptr); ++} +\ No newline at end of file +diff --git a/unittest/mock_helper.h b/unittest/mock_helper.h +new file mode 100644 +index 0000000..d116f57 +--- /dev/null ++++ b/unittest/mock_helper.h +@@ -0,0 +1,48 @@ ++/* 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_ ++ ++#include "password.h" ++ ++// define USER_SECRET_UT_DEBUG to output UT debug message. ++#define USER_SECRET_UT_DEBUG ++#if defined (USER_SECRET_UT_DEBUG) ++#define debug_printf printf ++#else ++#define debug_printf ++#endif ++ ++#define TEST_SCEANRIO_LOAD_USER_SECRET_SETTING 1 ++ ++/* 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/unittest/password_test.c b/unittest/password_test.c +new file mode 100644 +index 0000000..606ecc5 +--- /dev/null ++++ b/unittest/password_test.c +@@ -0,0 +1,199 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "mock_helper.h" ++#include "regex_helper.h" ++#include "sudoers_helper.h" ++#include "trace.h" ++#include "password.h" ++ ++/* Regex list */ ++extern REGEX_NODE *global_regex_list; ++ ++int clean_up() { ++ return 0; ++} ++ ++int start_up() { ++ return 0; ++} ++ ++/* Test load password setting*/ ++void testcase_load_password_setting() { ++ set_test_scenario(TEST_SCEANRIO_LOAD_USER_SECRET_SETTING); ++ initialize_password_setting("./sudoers"); ++ ++ int loaded_regex_count = 0; ++ REGEX_NODE *next_node = global_regex_list; ++ while (next_node != NULL) { ++ /* Continue with next pligin */ ++ REGEX_NODE* current_node_memory = next_node; ++ next_node = next_node->next; ++ ++ loaded_regex_count++; ++ } ++ ++ release_password_setting(); ++ ++ CU_ASSERT_EQUAL(loaded_regex_count, 4); ++} ++ ++/* Test convert setting string to regex string*/ ++void testcase_convert_passwd_cmd_to_regex() { ++ char regex_buffer[MAX_LINE_SIZE]; ++ ++ /* '*' in input setting should replace to (\S*) */ ++ convert_passwd_cmd_to_regex(regex_buffer, sizeof(regex_buffer), "testcommand *"); ++ debug_printf("regex_buffer: %s\n", regex_buffer); ++ CU_ASSERT_STRING_EQUAL(regex_buffer, "testcommand[[:space:]]*\\([^[:space:]]*\\)"); ++ ++ convert_passwd_cmd_to_regex(regex_buffer, sizeof(regex_buffer), "/usr/sbin/chpasswd *"); ++ debug_printf("regex_buffer: %s\n", regex_buffer); ++ CU_ASSERT_STRING_EQUAL(regex_buffer, "/usr/sbin/chpasswd[[:space:]]*\\([^[:space:]]*\\)"); ++} ++ ++/* Test fix password by regex*/ ++void testcase_fix_password_by_regex() { ++ char regex_buffer[MAX_LINE_SIZE]; ++ char result_buffer[MAX_LINE_SIZE]; ++ ++ /* '*' in input setting should replace to (\S*) */ ++ convert_passwd_cmd_to_regex(regex_buffer, sizeof(regex_buffer), "testcommand *"); ++ debug_printf("regex_buffer: %s\n", regex_buffer); ++ ++ /* Fixed regex should be a correct regex */ ++ regex_t regex; ++ CU_ASSERT_FALSE(regcomp(®ex, regex_buffer, REG_NEWLINE)); ++ ++ /* Password should be removed by regex */ ++ snprintf(result_buffer, sizeof(result_buffer), "%s", "testcommand testsecret"); ++ remove_password_by_regex(result_buffer, regex); ++ ++ debug_printf("Fixed command: %s\n", result_buffer); ++ CU_ASSERT_STRING_EQUAL(result_buffer, "testcommand **********"); ++} ++ ++/* Test fix password*/ ++void testcase_fix_password() { ++ char result_buffer[MAX_LINE_SIZE]; ++ initialize_password_setting("./sudoers"); ++ ++ /* Password should be removed by regex */ ++ snprintf(result_buffer, sizeof(result_buffer), "%s", "/usr/local/bin/config tacacs passkey testsecret"); ++ remove_password(result_buffer); ++ ++ debug_printf("Fixed command: %s\n", result_buffer); ++ CU_ASSERT_STRING_EQUAL(result_buffer, "/usr/local/bin/config tacacs passkey **********"); ++ ++ snprintf(result_buffer, sizeof(result_buffer), "%s", "/usr/sbin/chpasswd testsecret"); ++ remove_password(result_buffer); ++ ++ debug_printf("Fixed command: %s\n", result_buffer); ++ CU_ASSERT_STRING_EQUAL(result_buffer, "/usr/sbin/chpasswd **********"); ++ ++ snprintf(result_buffer, sizeof(result_buffer), "%s", "/usr/sbin/setpasswd testsecret"); ++ remove_password(result_buffer); ++ ++ debug_printf("Fixed command: %s\n", result_buffer); ++ CU_ASSERT_STRING_EQUAL(result_buffer, "/usr/sbin/setpasswd **********"); ++ ++ /* In sudoers file PASSWD_CMD cross multiple line should handle correctly */ ++ snprintf(result_buffer, sizeof(result_buffer), "%s", "/usr/sbin/unfinishedcommand testsecret ,"); ++ remove_password(result_buffer); ++ ++ debug_printf("Fixed command: %s\n", result_buffer); ++ CU_ASSERT_STRING_EQUAL(result_buffer, "/usr/sbin/unfinishedcommand ********** ,"); ++ ++ /* Regular command not change */ ++ snprintf(result_buffer, sizeof(result_buffer), "%s", "command no password"); ++ remove_password(result_buffer); ++ CU_ASSERT_STRING_EQUAL(result_buffer, "command no password"); ++ ++ release_password_setting(); ++} ++ ++/* Test release all regex */ ++void testcase_release_all_regex() { ++ set_memory_allocate_count(0); ++ ++ initialize_password_setting("./sudoers"); ++ release_password_setting(); ++ ++ /* All memory should free */ ++ CU_ASSERT_EQUAL(get_memory_allocate_count(), 0); ++} ++ ++/* Test for escape_characters method */ ++void testcase_escape_characters() { ++ char buffer[MAX_LINE_SIZE]; ++ ++ snprintf(buffer, sizeof(buffer), "%s", "Test string \\\\"); ++ escape_characters(buffer); ++ printf(buffer); ++ CU_ASSERT_TRUE(strcmp(buffer, "Test string \\") == 0); ++ CU_ASSERT_STRING_EQUAL(buffer, "Test string \\"); ++ ++ snprintf(buffer, sizeof(buffer), "%s", "Test string \\,"); ++ escape_characters(buffer); ++ CU_ASSERT_STRING_EQUAL(buffer, "Test string ,"); ++ ++ snprintf(buffer, sizeof(buffer), "%s", "Test string \\\\\\,"); ++ escape_characters(buffer); ++ CU_ASSERT_STRING_EQUAL(buffer, "Test string \\,"); ++ ++ /* Following case should not escape */ ++ snprintf(buffer, sizeof(buffer), "%s", "Test string \\:"); ++ escape_characters(buffer); ++ CU_ASSERT_STRING_EQUAL(buffer, "Test string \\:"); ++ ++ snprintf(buffer, sizeof(buffer), "%s", "Test string \\."); ++ escape_characters(buffer); ++ CU_ASSERT_STRING_EQUAL(buffer, "Test string \\."); ++} ++ ++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_load_password_setting()...\n", testcase_load_password_setting) ++ || !CU_add_test(ste, "Test testcase_convert_passwd_cmd_to_regex()...\n", testcase_convert_passwd_cmd_to_regex) ++ || !CU_add_test(ste, "Test testcase_fix_password_by_regex()...\n", testcase_fix_password_by_regex) ++ || !CU_add_test(ste, "Test testcase_fix_password()...\n", testcase_fix_password) ++ || !CU_add_test(ste, "Test testcase_release_all_regex()...\n", testcase_release_all_regex) ++ || !CU_add_test(ste, "Test testcase_escape_characters()...\n", testcase_escape_characters)) { ++ 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(); ++} +diff --git a/unittest/sudoers b/unittest/sudoers +new file mode 100644 +index 0000000..4e36873 +--- /dev/null ++++ b/unittest/sudoers +@@ -0,0 +1,5 @@ ++# test file for read user secret setting ++ ++Cmnd_Alias PASSWD_CMDS = /usr/local/bin/config tacacs passkey *, \ ++ /usr/sbin/unfinished\ ++command * \, ,/usr/sbin/chpasswd * , /usr/sbin/setpasswd * +\ No newline at end of file +-- +2.17.1.windows.2 + diff --git a/src/tacacs/audisp/patches/0003-Add-local-accounting.patch b/src/tacacs/audisp/patches/0003-Add-local-accounting.patch new file mode 100644 index 000000000..1883f5591 --- /dev/null +++ b/src/tacacs/audisp/patches/0003-Add-local-accounting.patch @@ -0,0 +1,97 @@ +From 79b07171c2c2651e535a468d7a56621a2a7049b9 Mon Sep 17 00:00:00 2001 +From: liuh-80 <58683130+liuh-80@users.noreply.github.com> +Date: Tue, 23 Nov 2021 13:34:00 +0800 +Subject: [PATCH 3/3] Add local accounting. + +--- + Makefile.am | 2 +- + audisp-tacplus.c | 11 ++++++++++- + local_accounting.c | 17 +++++++++++++++++ + local_accounting.h | 7 +++++++ + 4 files changed, 35 insertions(+), 2 deletions(-) + create mode 100644 local_accounting.c + create mode 100644 local_accounting.h + +diff --git a/Makefile.am b/Makefile.am +index b6cb92b..dacc8fd 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -4,7 +4,7 @@ + EXTRA_DIST = ChangeLog README audisp_tacplus.spec \ + audisp-tac_plus.conf audisp-tacplus.conf + +-audisp_tacplus_SOURCES = audisp-tacplus.c password.c regex_helper.c trace.c sudoers_helper.c ++audisp_tacplus_SOURCES = audisp-tacplus.c password.c regex_helper.c trace.c local_accounting.c sudoers_helper.c + audisp_tacplus_CFLAGS = -O + audisp_tacplus_LDADD = -lauparse -ltacsupport -ltac + sbin_PROGRAMS = audisp-tacplus +diff --git a/audisp-tacplus.c b/audisp-tacplus.c +index 229694b..7a4ab9e 100644 +--- a/audisp-tacplus.c ++++ b/audisp-tacplus.c +@@ -69,6 +69,9 @@ + #include "password.h" + #include "sudoers_helper.h" + ++/* Local accounting */ ++#include "local_accounting.h" ++ + #define _VMAJ 1 + #define _VMIN 0 + #define _VPATCH 0 +@@ -526,7 +529,13 @@ static void get_acct_record(auparse_state_t *au, int type) + * unknown, and in some cases, host may be not be set. + */ + remove_password(logbase); +- send_tacacs_acct(loguser, tty?tty:"UNK", host?host:"UNK", logbase, acct_type, taskno); ++ if (tacacs_ctrl & ACCOUNTING_FLAG_TACACS) { ++ send_tacacs_acct(loguser, tty?tty:"UNK", host?host:"UNK", logbase, acct_type, taskno); ++ } ++ ++ if (tacacs_ctrl & ACCOUNTING_FLAG_LOCAL) { ++ accounting_to_syslog(loguser, tty?tty:"UNK", host?host:"UNK", logbase, acct_type, taskno); ++ } + + if(host) + free(host); +diff --git a/local_accounting.c b/local_accounting.c +new file mode 100644 +index 0000000..e23acec +--- /dev/null ++++ b/local_accounting.c +@@ -0,0 +1,17 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "trace.h" ++ ++/* Accounting log format. */ ++#define ACCOUNTING_LOG_FORMAT "Accounting: user: %s, tty: %s, host: %s, command: %s, type: %d, task ID: %d" ++ ++/* Write the accounting information to syslog. */ ++void accounting_to_syslog(char *user, char *tty, char *host, char *cmdmsg, int type, uint16_t task_id) ++{ ++ trace(ACCOUNTING_LOG_FORMAT, user, tty, host, cmdmsg, type, task_id); ++} +\ No newline at end of file +diff --git a/local_accounting.h b/local_accounting.h +new file mode 100644 +index 0000000..9e880a7 +--- /dev/null ++++ b/local_accounting.h +@@ -0,0 +1,7 @@ ++#ifndef LOCAL_ACCOUNTING_H ++#define LOCAL_ACCOUNTING_H ++ ++/* Write accounting information to syslog. */ ++extern void accounting_to_syslog(char *user, char *tty, char *host, char *cmdmsg, int type, uint16_t task_id); ++ ++#endif /* LOCAL_ACCOUNTING_H */ +\ No newline at end of file +-- +2.17.1.windows.2 + diff --git a/src/tacacs/audisp/patches/series b/src/tacacs/audisp/patches/series new file mode 100644 index 000000000..20a7e8e64 --- /dev/null +++ b/src/tacacs/audisp/patches/series @@ -0,0 +1,3 @@ +0001-Porting-to-sonic.patch +0002-Remove-user-secret-from-accounting-log.patch +0003-Add-local-accounting.patch \ No newline at end of file