summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Gollub <dgollub@att.com>2019-11-11 15:07:38 +0100
committerVyatta Package Maintainers <DL-vyatta-help@att.com>2019-11-13 17:08:02 +0000
commit822c8f60b72cca97fb2c86db37835a60917d1c7e (patch)
treed5593a4ed1cbb8e9709411679974355a41f0289e
downloadtacplusd-danos/1908.tar.gz
tacplusd-danos/1908.zip
-rw-r--r--.gitignore13
l---------ChangeLog1
-rw-r--r--LICENSE343
-rw-r--r--Makefile.am1
-rw-r--r--README9
-rw-r--r--configure.ac42
-rw-r--r--debian/.gitignore12
-rw-r--r--debian/changelog5
-rw-r--r--debian/compat1
-rw-r--r--debian/control25
-rw-r--r--debian/copyright56
-rw-r--r--debian/lintian-overrides1
-rwxr-xr-xdebian/rules17
-rw-r--r--debian/source/format1
-rw-r--r--debian/tacplusd.init56
-rw-r--r--debian/tacplusd.install2
-rw-r--r--debian/tacplusd.service16
-rw-r--r--tacplus-daemon/.gitignore3
-rw-r--r--tacplus-daemon/Makefile.am32
-rw-r--r--tacplus-daemon/daemon.c60
-rw-r--r--tacplus-daemon/daemon.h20
-rw-r--r--tacplus-daemon/dbus_service.c907
-rw-r--r--tacplus-daemon/dbus_service.h36
-rw-r--r--tacplus-daemon/global.c106
-rw-r--r--tacplus-daemon/global.h38
-rw-r--r--tacplus-daemon/main.c192
-rw-r--r--tacplus-daemon/main.h12
-rw-r--r--tacplus-daemon/net.vyatta.tacplus.conf9
-rw-r--r--tacplus-daemon/parser.c317
-rw-r--r--tacplus-daemon/parser.h30
-rw-r--r--tacplus-daemon/queue.c127
-rw-r--r--tacplus-daemon/queue.h38
-rw-r--r--tacplus-daemon/statistics.c134
-rw-r--r--tacplus-daemon/statistics.h50
-rw-r--r--tacplus-daemon/tacplus_srv_conn.c595
-rw-r--r--tacplus-daemon/tacplus_srv_conn.h149
-rw-r--r--tacplus-daemon/tacplusd.suppress31
-rw-r--r--tacplus-daemon/test/Makefile.am52
-rw-r--r--tacplus-daemon/test/config/typical19
-rw-r--r--tacplus-daemon/test/config/typical-broadcast18
-rw-r--r--tacplus-daemon/test/config/typical-changed-hold-down18
-rw-r--r--tacplus-daemon/test/parserTester.cpp221
-rw-r--r--tacplus-daemon/test/queueTester.cpp143
-rw-r--r--tacplus-daemon/test/serverConnectTester.cpp1527
-rw-r--r--tacplus-daemon/test/testMain.cpp19
-rw-r--r--tacplus-daemon/test/transactionTester.cpp276
-rw-r--r--tacplus-daemon/test/ut_utils.c17
-rw-r--r--tacplus-daemon/test/ut_utils.h189
-rw-r--r--tacplus-daemon/test/utilsTester.cpp324
-rw-r--r--tacplus-daemon/transaction.c519
-rw-r--r--tacplus-daemon/transaction.h101
-rw-r--r--tacplus-daemon/transaction_private.h19
-rw-r--r--tacplus-daemon/utils.c330
-rw-r--r--tacplus-daemon/utils.h62
54 files changed, 7341 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fe9c6b0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+autom4te.cache/
+COPYING
+INSTALL
+Makefile
+Makefile.in
+aclocal.m4
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
diff --git a/ChangeLog b/ChangeLog
new file mode 120000
index 0000000..d526672
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1 @@
+debian/changelog \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c05dcb2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,343 @@
+This repository uses SPDX (https://spdx.org/) tags.
+
+Files without a "SPDX-License-Identifier" tag or with the "SPDX-License-Identifier: GPL-2.0-only" tag are available under the following license:
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..366d33a
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = tacplus-daemon
diff --git a/README b/README
new file mode 100644
index 0000000..21d153a
--- /dev/null
+++ b/README
@@ -0,0 +1,9 @@
+# tacplusd
+
+TACACS+ client daemon with a D-Bus interface based on libtac from pam_tacplus.
+
+Currently supported TACACS+ functionality:
+
+ * Accounting (Round-Robin and Broadcast)
+ * Authorization
+ * Authentication
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..ec08496
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,42 @@
+AC_PREREQ(2.69)
+
+m4_define([VERSION_ID], [m4_esyscmd([
+ echo -n `dpkg-parsechangelog | sed -n 's/^Version: //p'`
+ ])])
+
+AC_INIT([tacplusd], VERSION_ID, [DL-vyatta-help@att.com])
+
+#AC_CONFIG_AUX_DIR([config])
+AM_INIT_AUTOMAKE([1.11.1 foreign no-dist-gzip dist-bzip2 subdir-objects parallel-tests])
+
+AC_PROG_CC
+AC_PROG_CXX
+AM_PROG_AS
+AM_PROG_CC_C_O
+
+PKG_CHECK_MODULES([SYSTEMD],[libsystemd])
+PKG_CHECK_MODULES([LIBTAC],[libtac])
+PKG_CHECK_MODULES([LIBTAC_EVENT],[libtac-event],
+ [AC_DEFINE([HAVE_LIBTAC_EVENT], [1], [libtac-event present])],
+ true
+)
+PKG_CHECK_MODULES([GLIB],[glib-2.0])
+
+AC_CONFIG_FILES([
+ Makefile
+ tacplus-daemon/Makefile
+ tacplus-daemon/test/Makefile
+])
+
+PKG_CHECK_MODULES(cpputest, [cpputest], [], [
+ dnl Fall back to classic searching. 3.1 on Wheezy doesn't supply .pc
+ AC_LANG_CPLUSPLUS
+ AC_CHECK_LIB([CppUTest], [main], [
+ have_cpputest=yes
+ cpputest_LIBS="-lstdc++ -lCppUTest -lCppUTestExt"
+ cpputest_CFLAGS=""],
+ [AC_MSG_ERROR(cpputest is required for this program)])
+ AC_LANG_C ]
+)
+
+AC_OUTPUT
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644
index 0000000..5a243b9
--- /dev/null
+++ b/debian/.gitignore
@@ -0,0 +1,12 @@
+autoreconf.after
+autoreconf.before
+files
+*.debhelper.log
+*.substvars
+*.debhelper.log
+*.substvars
+*.postinst.debhelper
+*.postrm.debhelper
+tacplusd-dbg/
+tacplusd/
+tmp/
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..acd6ed8
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+tacplusd (1.19) unstable; urgency=medium
+
+ * DANOS Import
+
+ -- Vyatta Package Maintainers <DL-vyatta-help@att.com> Sat, 09 Nov 2019 11:02:32 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..f44f209
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,25 @@
+Source: tacplusd
+Section: contrib/net
+Priority: extra
+Maintainer: Vyatta Package Maintainers <DL-vyatta-help@att.com>
+Build-Depends: debhelper (>= 9), autoconf, automake, autotools-dev,
+ libsystemd-dev, libtac-dev (>= 1.3.9-0vyatta11), libglib2.0-dev,
+ pkg-config, cpio, dh-systemd, dh-autoreconf, cpputest, libcpputest-dev
+Standards-Version: 3.9.8
+
+Package: tacplusd
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}, lsb-base (>= 3.0-6)
+Provides: vyatta-tacplus-daemon
+Replaces: vyatta-tacplus-daemon
+Conflicts: vyatta-tacplus-daemon
+Description: TACACS+ daemon used to forward and relay TACACS+
+ requests and responses to dbus clients.
+
+Package: tacplusd-dbg
+Architecture: any
+Priority: extra
+Section: contrib/debug
+Depends: ${misc:Depends}, tacplusd (= ${binary:Version})
+Description: tacplusd debugging symbols
+ The debugging symbols for tacplusd package.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..2e43887
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,56 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: debian/*
+Copyright: 2016 Brocade Communications Systems, Inc.
+ 2018-2019 AT&T Intellectual Property.
+License: GPL-2.0
+
+Files: tacplus-daemon/daemon.c
+ tacplus-daemon/daemon.h
+ tacplus-daemon/dbus_service.c
+ tacplus-daemon/dbus_service.h
+ tacplus-daemon/main.c
+ tacplus-daemon/main.h
+ tacplus-daemon/parser.c
+ tacplus-daemon/parser.h
+ tacplus-daemon/queue.c
+ tacplus-daemon/queue.h
+ tacplus-daemon/statistics.c
+ tacplus-daemon/statistics.h
+ tacplus-daemon/tacplus_srv_conn.c
+ tacplus-daemon/tacplus_srv_conn.h
+ tacplus-daemon/test/parserTester.cpp
+ tacplus-daemon/test/queueTester.cpp
+ tacplus-daemon/test/serverConnectTester.cpp
+ tacplus-daemon/test/testMain.cpp
+ tacplus-daemon/test/transactionTester.cpp
+ tacplus-daemon/test/ut_utils.c
+ tacplus-daemon/test/ut_utils.h
+ tacplus-daemon/test/utilsTester.cpp
+ tacplus-daemon/transaction.c
+ tacplus-daemon/transaction.h
+ tacplus-daemon/transaction_private.h
+ tacplus-daemon/utils.c
+ tacplus-daemon/utils.h
+Copyright: 2015-2016 Brocade Communications Systems, Inc.
+ 2018-2019 AT&T Intellectual Property.
+License: GPL-2.0
+ 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;
+ version 2.
+ .
+ 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 package; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA 02110-1301 USA
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
diff --git a/debian/lintian-overrides b/debian/lintian-overrides
new file mode 100644
index 0000000..abf4dda
--- /dev/null
+++ b/debian/lintian-overrides
@@ -0,0 +1 @@
+tacplusd: binary-without-manpage usr/sbin/tacplusd
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..d029fd3
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,17 @@
+#!/usr/bin/make -f
+
+%:
+ dh $@ --with autoreconf,systemd
+
+# Don't start any daemon by default. This is up to configd.
+override_dh_systemd_enable:
+ dh_systemd_enable --name=tacplusd --no-enable
+
+override_dh_installinit:
+ dh_installinit --no-start
+
+override_dh_systemd_start:
+ dh_systemd_start --no-start
+
+override_dh_strip:
+ dh_strip --dbg-package=tacplusd-dbg
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/debian/tacplusd.init b/debian/tacplusd.init
new file mode 100644
index 0000000..71b06d8
--- /dev/null
+++ b/debian/tacplusd.init
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+### BEGIN INIT INFO
+# Provides: tacplusd
+# Required-Start: $local_fs $network $remote_fs $syslog
+# Required-Stop: $local_fs $network $remote_fs $syslog
+# Default-Start:
+# Default-Stop:
+# Short-Description: start and stop Tacplus daemon
+### END INIT INFO
+#
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin
+
+. /lib/lsb/init-functions
+
+DAEMON=/usr/sbin/tacplusd
+PIDFILE=/var/run/tacplusd.pid
+OPTIONS=
+
+case $1 in
+ start)
+ log_daemon_msg "Starting TACACS+ daemon" "tacplusd"
+ start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE \
+ --startas $DAEMON -- -p $PIDFILE $OPTIONS
+ log_end_msg $?
+ ;;
+ stop)
+ log_daemon_msg "Stopping TACACS+ daemon" "tacplusd"
+ start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE
+ log_end_msg $?
+ rm -f $PIDFILE
+ rm -r $ENVFILE
+ ;;
+ restart|force-reload)
+ $0 stop && sleep 2 && $0 start
+ ;;
+ try-restart)
+ if $0 status >/dev/null; then
+ $0 restart
+ else
+ exit 0
+ fi
+ ;;
+ reload)
+ pkill -HUP tacplusd
+ ;;
+ status)
+ status_of_proc $DAEMON "TACACS+ daemon server"
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|restart|try-restart|force-reload|status}"
+ exit 2
+ ;;
+esac
+
diff --git a/debian/tacplusd.install b/debian/tacplusd.install
new file mode 100644
index 0000000..7df0090
--- /dev/null
+++ b/debian/tacplusd.install
@@ -0,0 +1,2 @@
+usr/sbin/tacplusd
+etc/dbus-1/system.d/net.vyatta.tacplus.conf
diff --git a/debian/tacplusd.service b/debian/tacplusd.service
new file mode 100644
index 0000000..c6ffd4f
--- /dev/null
+++ b/debian/tacplusd.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Tacacs+ D-Bus Daemon
+
+[Service]
+Type=forking
+EnvironmentFile=/var/run/tacplus.env
+ExecReload=/bin/kill -SIGHUP $MAINPID
+ExecStart=/usr/sbin/tacplusd $CONFIG /run/tacplusd-default.pid
+KillSignal=SIGTERM
+PIDFile=/run/tacplusd-default.pid
+Restart=on-failure
+# should be maximum socket timeout permitted
+TimeoutStopSec=30
+
+[Install]
+WantedBy=multi-user.target
diff --git a/tacplus-daemon/.gitignore b/tacplus-daemon/.gitignore
new file mode 100644
index 0000000..ea08a84
--- /dev/null
+++ b/tacplus-daemon/.gitignore
@@ -0,0 +1,3 @@
+*.o
+.deps
+tacplusd
diff --git a/tacplus-daemon/Makefile.am b/tacplus-daemon/Makefile.am
new file mode 100644
index 0000000..09253c9
--- /dev/null
+++ b/tacplus-daemon/Makefile.am
@@ -0,0 +1,32 @@
+SUBDIRS = test
+
+dbusconfdir = $(sysconfdir)/dbus-1/system.d
+
+
+AM_CFLAGS = $(LIBTAC_CFLAGS) $(LIBTAC_EVENT_CFLAGS) $(SYSTEMD_CFLAGS) $(GLIB_CFLAGS)
+AM_CFLAGS += -Wall -Wextra -Werror
+
+sbin_PROGRAMS = tacplusd
+
+tacplusd_SOURCES = \
+ queue.c \
+ dbus_service.c \
+ main.c \
+ daemon.c \
+ tacplus_srv_conn.c \
+ transaction.c \
+ parser.c \
+ utils.c \
+ statistics.c \
+ global.c
+
+tacplusd_LDADD = \
+ $(LIBTAC_LIBS) \
+ $(LIBTAC_EVENT_LIBS) \
+ $(SYSTEMD_LIBS) \
+ $(GLIB_LIBS) \
+ -lpthread \
+ -lm \
+ -lrt
+
+dist_dbusconf_DATA = net.vyatta.tacplus.conf
diff --git a/tacplus-daemon/daemon.c b/tacplus-daemon/daemon.c
new file mode 100644
index 0000000..0f1e2e4
--- /dev/null
+++ b/tacplus-daemon/daemon.c
@@ -0,0 +1,60 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "daemon.h"
+
+/* TODO
+ * -use strerror_r() instead of strerror()
+ */
+
+
+/* TODO: Take filename from command line */
+static void record_pid(const char *fname)
+{
+ FILE *f = fopen(fname, "w");
+
+ if (f == NULL) {
+ /* TODO: append strerr() */
+ syslog(LOG_ERR, "Failed to open pid file: %s", fname);
+ return;
+ }
+
+ fprintf(f, "%u\n", getpid());
+ fclose(f);
+}
+
+void daemonize(const char *tacplus_pid)
+{
+ /* 0 returned in child */
+ if (fork() != 0) {
+ exit(EXIT_SUCCESS); /* parent exit */
+ }
+
+ /* Become new process group leader */
+ if (setsid() < 0) {
+ fprintf(stderr, "setsid() failed. Error: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Overwrite parent's cwd */
+ if(chdir("/") < 0) {
+ fprintf(stderr, "Changing cwd to root directory failed. Error: %s",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ /* Close standard file descriptors */
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+ /* Reset file permissions */
+ umask(0);
+ record_pid(tacplus_pid);
+}
+
diff --git a/tacplus-daemon/daemon.h b/tacplus-daemon/daemon.h
new file mode 100644
index 0000000..fe7a137
--- /dev/null
+++ b/tacplus-daemon/daemon.h
@@ -0,0 +1,20 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* prototypes */
+extern void daemonize(const char *tacplus_pid);
+
diff --git a/tacplus-daemon/dbus_service.c b/tacplus-daemon/dbus_service.c
new file mode 100644
index 0000000..b56d26a
--- /dev/null
+++ b/tacplus-daemon/dbus_service.c
@@ -0,0 +1,907 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <systemd/sd-bus.h>
+
+#include "dbus_service.h"
+#include "global.h"
+#include "queue.h"
+#include "statistics.h"
+#include "transaction.h"
+
+#define __unused __attribute__((unused))
+
+/* Abstraction of commonly used types */
+#define BUS_TYPE_ARRAY "a" /* SD_BUS_TYPE_ARRAY */
+#define BUS_TYPE_INT32 "i" /* SD_BUS_TYPE_INT32 */
+#define BUS_TYPE_STRING "s" /* SD_BUS_TYPE_STRING */
+
+#define BUS_TYPE_DICT_ELEM(K,V) \
+ "{" /* SD_BUS_TYPE_DICT_ENTRY_BEGIN */ \
+ K V \
+ "}" /* SD_BUS_TYPE_DICT_ENTRY_END */
+
+#define BUS_TYPE_DICT(K,V) BUS_TYPE_ARRAY BUS_TYPE_DICT_ELEM(K,V)
+
+#define BUS_TYPE_STR_STR_DICT_ELEM BUS_TYPE_DICT_ELEM(BUS_TYPE_STRING, BUS_TYPE_STRING)
+#define BUS_TYPE_STR_STR_DICT BUS_TYPE_DICT(BUS_TYPE_STRING, BUS_TYPE_STRING)
+
+/* DBus method signatures */
+#define GET_STATUS_ARGS ""
+#define GET_STATUS_RET BUS_TYPE_ARRAY BUS_TYPE_STRING
+
+#define ACCOUNT_SEND_ARGS BUS_TYPE_INT32 BUS_TYPE_STRING BUS_TYPE_STRING \
+ BUS_TYPE_STRING BUS_TYPE_STR_STR_DICT
+#define ACCOUNT_SEND_RET BUS_TYPE_INT32
+
+#define CMD_ACCOUNT_SEND_ARGS ACCOUNT_SEND_ARGS BUS_TYPE_ARRAY BUS_TYPE_STRING
+#define CMD_ACCOUNT_SEND_RET ACCOUNT_SEND_RET
+
+#define AUTHEN_SEND_ARGS BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING
+#define AUTHEN_SEND_RET BUS_TYPE_INT32
+
+#define AUTHOR_SEND_ARGS BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STR_STR_DICT
+#define AUTHOR_SEND_RET BUS_TYPE_INT32 BUS_TYPE_STR_STR_DICT
+
+#define CMD_AUTHOR_SEND_ARGS AUTHOR_SEND_ARGS BUS_TYPE_ARRAY BUS_TYPE_STRING
+#define CMD_AUTHOR_SEND_RET AUTHOR_SEND_RET
+
+struct tacplus_dbus_service {
+ sd_bus *bus;
+ bool stop;
+ bool process;
+
+ Queue *tacacs_query_q;
+ Queue *tacacs_response_q;
+
+ pthread_mutex_t bus_lock;
+ pthread_t request_thread, reply_thread, dbus_thread;
+
+ uint64_t acct_task_id;
+};
+
+static struct tacplus_dbus_service _service = { .stop = true, .process = false };
+static tacplus_dbus_service_t service = &_service;
+
+static void transaction_queue_free_element(void *e)
+{
+ struct transaction *t = e;
+
+ /* To cleanly exit, we add bogus entries to our queues that trick
+ * our threads into returning from pthread_cond_wait. These bogus
+ * queue elements are allocated with calloc, hence the check for
+ * NULL.
+ */
+ if (t->user)
+ sd_bus_message_unref(t->user);
+
+ /* Do any per-type cleanup */
+ switch (t->type) {
+ case TRANSACTION_ACCOUNT:
+ free(t->request.account.task_id);
+ free(t->request.account.r_addr);
+ for (char **arg = t->request.account.command; arg && *arg; arg++)
+ free(*arg);
+ free(t->request.account.command);
+ break;
+ case TRANSACTION_AUTHOR:
+ for (char **arg = t->request.author.cmd; arg && *arg; arg++)
+ free(*arg);
+ free(t->request.author.cmd);
+ break;
+ case TRANSACTION_AUTHEN:
+ default:
+ break;
+ }
+
+ transaction_free(&t);
+}
+
+void dbus_service_init()
+{
+ pthread_mutex_init(&service->bus_lock, NULL);
+
+ service->tacacs_query_q = create_queue(transaction_queue_free_element);
+ service->tacacs_response_q = create_queue(transaction_queue_free_element);
+}
+
+void dbus_service_deinit()
+{
+ destroy_queue(&service->tacacs_query_q);
+ destroy_queue(&service->tacacs_response_q);
+
+ pthread_mutex_lock(&service->bus_lock);
+ sd_bus_release_name(service->bus, TACPLUS_DAEMON);
+ sd_bus_close(service->bus);
+ sd_bus_unref(service->bus);
+ service->bus = NULL;
+ pthread_mutex_unlock(&service->bus_lock);
+
+ pthread_mutex_destroy(&service->bus_lock);
+}
+
+static int
+fill_bus_msg_from_account_transaction(const struct account_send_response *r,
+ sd_bus_message *m)
+{
+ return sd_bus_message_append(m, BUS_TYPE_INT32, r->status);
+}
+
+static int
+fill_bus_msg_from_authen_transaction(const struct authen_send_response *r,
+ sd_bus_message *m)
+{
+ return sd_bus_message_append(m, BUS_TYPE_INT32, r->status);
+}
+
+static int
+fill_bus_msg_from_author_transaction(const struct author_send_response *r,
+ sd_bus_message *m)
+{
+ int type, ret;
+
+ switch (r->status) {
+ case TAC_PLUS_AUTHOR_STATUS_PASS_ADD:
+ case TAC_PLUS_AUTHOR_STATUS_PASS_REPL:
+ case TAC_PLUS_AUTHOR_STATUS_FAIL:
+ case TAC_PLUS_AUTHOR_STATUS_ERROR:
+ type = r->status;
+ break;
+ case TAC_PLUS_AUTHOR_STATUS_FOLLOW:
+ default:
+ type = TAC_PLUS_AUTHOR_STATUS_ERROR;
+ break;
+ }
+
+ ret = sd_bus_message_append(m, BUS_TYPE_INT32, type);
+ if (ret < 0)
+ return ret;
+
+ sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, BUS_TYPE_STR_STR_DICT_ELEM);
+ if (ret < 0)
+ return ret;
+
+ for (struct transaction_attrib *attr = r->attrs; attr; attr = attr->next) {
+ ret = sd_bus_message_append(m, BUS_TYPE_STR_STR_DICT_ELEM, attr->name, attr->value);
+ if (ret < 0)
+ return ret;
+ }
+
+ return sd_bus_message_close_container(m);
+}
+
+static sd_bus_message *
+fill_bus_msg_for_transaction(struct transaction *t)
+{
+ sd_bus_message *reply;
+ int ret;
+
+ if (! t->user) {
+ syslog(LOG_ERR, "Cannot generate method response without method call message");
+ return NULL;
+ }
+
+ ret = sd_bus_message_new_method_return(t->user, &reply);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to allocate method response message: %d", ret);
+ return NULL;
+ }
+
+ switch (t->type) {
+ case TRANSACTION_ACCOUNT:
+ ret = fill_bus_msg_from_account_transaction(&t->response.account, reply);
+ break;
+ case TRANSACTION_AUTHEN:
+ ret = fill_bus_msg_from_authen_transaction(&t->response.authen, reply);
+ break;
+ case TRANSACTION_AUTHOR:
+ ret = fill_bus_msg_from_author_transaction(&t->response.author, reply);
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ if (ret < 0) {
+ sd_bus_message_unref(reply);
+ return NULL;
+ }
+
+ return reply;
+}
+
+static void release_transaction_for_bus_message(struct transaction **t)
+{
+ if (t && *t) {
+ transaction_queue_free_element(*t);
+ *t = NULL;
+ }
+}
+
+static void *consume_dbus_reply_thread(void *arg __unused)
+{
+ while (! service->stop) {
+ struct transaction *t;
+
+ pthread_mutex_lock(&(service->tacacs_response_q->lock));
+ while (is_queue_empty(service->tacacs_response_q)) {
+ pthread_cond_wait(&(service->tacacs_response_q->empty),
+ &(service->tacacs_response_q->lock));
+ }
+ pthread_mutex_unlock(&(service->tacacs_response_q->lock));
+
+ /* TODO: what if there's still a valid msg to send? */
+ if (service->stop || !service->process) {
+ syslog(LOG_DEBUG, "TACACS+ reply_thread: stopping");
+ break;
+ }
+
+ t = dequeue(service->tacacs_response_q);
+ if (t->type == TRANSACTION_INVALID) {
+ release_transaction_for_bus_message(&t);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "Processing %s transaction response",
+ transaction_type_str(t->type));
+
+ pthread_mutex_lock(&service->bus_lock);
+
+ sd_bus_message *reply = fill_bus_msg_for_transaction(t);
+ if (reply) {
+ int ret = sd_bus_send(sd_bus_message_get_bus(reply), reply, NULL);
+
+ if (ret < 0)
+ syslog(LOG_DEBUG, "Failed to send %s transaction response: %d",
+ transaction_type_str(t->type), ret);
+ else
+ syslog(LOG_DEBUG, "Sent %s transaction response",
+ transaction_type_str(t->type));
+
+ sd_bus_message_unref(reply);
+ reply = NULL;
+ }
+ else {
+ syslog(LOG_ERR, "Failed to generate response for transaction");
+ }
+
+ pthread_mutex_unlock(&service->bus_lock);
+
+ release_transaction_for_bus_message(&t);
+ }
+
+ syslog(LOG_DEBUG, "TACACS+ reply_thread: exiting");
+ pthread_exit(NULL);
+}
+
+static void *consume_dbus_req_thread(void *arg __unused)
+{
+ while (! service->stop) {
+ struct transaction *t;
+
+ pthread_mutex_lock(&(service->tacacs_query_q->lock));
+ while (is_queue_empty(service->tacacs_query_q)) {
+ pthread_cond_wait(&(service->tacacs_query_q->empty),
+ &(service->tacacs_query_q->lock));
+ }
+ pthread_mutex_unlock(&(service->tacacs_query_q->lock));
+
+ /* at this point we now have at least one tacacs+ request in our queue */
+
+ if (service->stop || !service->process) {
+ syslog(LOG_DEBUG, "TACACS+ request_thread: stopping");
+ break;
+ }
+
+ t = dequeue(service->tacacs_query_q);
+ if (t->type == TRANSACTION_INVALID) {
+ release_transaction_for_bus_message(&t);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "Processing %s transaction from queue",
+ transaction_type_str(t->type));
+
+ switch (t->type) {
+ case TRANSACTION_ACCOUNT:
+ tacplus_acct_send(t);
+ break;
+ case TRANSACTION_AUTHEN:
+ tacplus_authen_send(t);
+ break;
+ case TRANSACTION_AUTHOR:
+ tacplus_author_send(t);
+ break;
+ default:
+ syslog(LOG_ERR, "Unknown transaction type %d - ignoring", t->type);
+ release_transaction_for_bus_message(&t);
+ continue;
+ }
+
+ syslog(LOG_DEBUG, "Completed %s transaction, queueing response",
+ transaction_type_str(t->type));
+
+ enqueue(service->tacacs_response_q, t);
+ }
+
+ syslog(LOG_DEBUG, "TACACS+ request_thread: exiting");
+ pthread_exit(NULL);
+}
+
+static int fill_status_reply(struct sd_bus_message *m)
+{
+ int ret;
+
+ ret = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, BUS_TYPE_STRING);
+ if (ret < 0)
+ return ret;
+
+ for (unsigned i = 0 ; i < connControl->opts->n_servers ; i++) {
+ uint16_t port;
+ char *addr;
+ char *src = NULL;
+ char *val;
+ size_t val_size;
+ FILE *val_stream;
+
+ addr = addrinfo_to_string(tacplus_server(connControl->opts, i)->addrs);
+ if (! addr)
+ continue;
+
+ if (connControl->opts->server[i].src_addrs)
+ src = addrinfo_to_string(connControl->opts->server[i].src_addrs);
+ else if (connControl->opts->server[i].src_intf)
+ src = strdup(connControl->opts->server[i].src_intf);
+
+ port = get_addrinfo_port(tacplus_server(connControl->opts, i)->addrs);
+
+ val_stream = open_memstream(&val, &val_size);
+
+ fprintf(val_stream, "%s,%d,%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%ld",
+ addr,
+ port,
+ src ? src : "",
+ get_authen_requests(i),
+ get_authen_replies(i),
+ get_author_requests(i),
+ get_author_replies(i),
+ get_acct_requests(i),
+ get_acct_replies(i),
+ get_unknown_replies(i),
+ get_failed_connects(i),
+ (i == connControl->opts->curr_server) ? true : false,
+ tacplus_server_remaining_hold_down_secs(&connControl->opts->server[i]));
+
+ fclose(val_stream);
+
+ sd_bus_message_append(m, BUS_TYPE_STRING, val);
+
+ free(addr);
+ free(val);
+ free(src);
+ }
+
+ return sd_bus_message_close_container(m);
+}
+
+static int get_status(sd_bus_message *m,
+ __unused void *userdata,
+ __unused sd_bus_error *error)
+{
+ int ret;
+ sd_bus_message *reply;
+
+ syslog(LOG_DEBUG, "get_status() call");
+
+ ret = sd_bus_message_new_method_return(m, &reply);
+ if (ret < 0)
+ return ret;
+
+ ret = fill_status_reply(reply);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to generate status reply: %d", ret);
+ sd_bus_message_unref(reply);
+ return ret;
+ }
+
+ ret = sd_bus_send(sd_bus_message_get_bus(m), reply, NULL);
+ if (ret < 0)
+ syslog(LOG_DEBUG, "Failed to send get_status() response: %d", ret);
+
+ sd_bus_message_unref(reply);
+ return ret;
+}
+
+static int queue_transaction_for_bus_message(struct transaction *t,
+ sd_bus_message *m)
+{
+ /*
+ * Stash message with the transaction so we can send a reply later. We
+ * must add a ref to ensure the sd-bus library doesn't free the message
+ * until we are done.
+ */
+ t->user = m;
+ sd_bus_message_ref(m);
+
+ enqueue(service->tacacs_query_q, t);
+
+ syslog(LOG_DEBUG, "Queued %s transaction request",
+ transaction_type_str(t->type));
+
+ /* Return non-zero to allow us to send an asynchronous reply */
+ return 1;
+}
+
+typedef int (*account_variant_fill_handler)(struct account_send_param *,
+ sd_bus_message *);
+
+static int fill_account_transaction_from_bus_msg(struct account_send_param *p,
+ sd_bus_message *m,
+ account_variant_fill_handler var_fn)
+{
+ int ret;
+ char *key = NULL, *value = NULL;
+
+ ret = sd_bus_message_read(m, BUS_TYPE_INT32 BUS_TYPE_STRING
+ BUS_TYPE_STRING BUS_TYPE_STRING,
+ &p->account_flag, &p->name, &p->tty, &p->r_addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+ BUS_TYPE_STR_STR_DICT_ELEM);
+ if (ret < 0)
+ return ret;
+
+ while (sd_bus_message_read(m, BUS_TYPE_STR_STR_DICT_ELEM, &key, &value) > 0) {
+ if (strcmp("task_id", key) == 0) {
+ p->task_id = value;
+ }
+ else if (strcmp("start_time", key) == 0) {
+ p->start_time = value;
+ }
+ else if (strcmp("stop_time", key) == 0) {
+ p->stop_time = value;
+ }
+ else if (strcmp("service", key) == 0) {
+ p->service = value;
+ }
+ else if (strcmp("protocol", key) == 0) {
+ p->protocol = value;
+ }
+ else {
+ syslog(LOG_ERR, "Ignoring unsupported attribute-key: %s", key);
+ }
+ }
+
+ ret = sd_bus_message_exit_container(m);
+ if (ret < 0 || !var_fn)
+ return ret;
+
+ return var_fn(p, m);
+}
+
+static void fill_account_transaction_task_id(struct account_send_param *p) {
+ /*
+ * Choose a new task ID if one was not provided
+ *
+ * Otherwise copy the provided ID. This allows common cleanup in
+ * transaction_queue_free_element() since the memory lifecycle of the
+ * data filled into the transaction by fill_account_transaction_from_bus_msg()
+ * is managed by sd-dbus. Hence we also don't free any existing value.
+ */
+ if (!p->task_id) {
+ char buf[TAC_PLUS_ATTRIB_MAX_LEN] = {0};
+ assert(snprintf(buf, sizeof(buf), "%lu", service->acct_task_id++) < (int) sizeof(buf));
+ p->task_id = strdup(buf);
+ } else {
+ p->task_id = strdup(p->task_id);
+ }
+}
+
+static void fill_account_transaction_rem_addr(struct account_send_param *p) {
+ /*
+ * If we don't have a remote login address then attempt to obtain one based
+ * on the TTY (if we were passed one).
+ *
+ * Otherwise copy the provided address. This allows common cleanup in
+ * transaction_queue_free_element() since the memory lifecycle of the
+ * data filled into the transaction by fill_account_transaction_from_bus_msg()
+ * is managed by sd-dbus. Hence we also don't free any existing value.
+ */
+ char *r_addr;
+ if ((!p->r_addr || strlen(p->r_addr) == 0) &&
+ (r_addr = get_tty_login_addr(p->tty))) {
+ p->r_addr = r_addr;
+ } else {
+ p->r_addr = strdup(p->r_addr);
+ }
+}
+
+#define ACCOUNT_SEND_VARIANT(N, V) \
+static int N(sd_bus_message *m, \
+ __unused void *userdata, \
+ __unused sd_bus_error *error) \
+{ \
+ struct transaction *t; \
+ int ret; \
+ \
+ syslog(LOG_DEBUG, #N "() call"); \
+ \
+ t = transaction_new(TRANSACTION_ACCOUNT); \
+ if (!t) \
+ return -ENOMEM; \
+ \
+ ret = fill_account_transaction_from_bus_msg(&t->request.account, m, V); \
+ if (ret < 0) { \
+ syslog(LOG_ERR, "Failed to parse " #N " call: %d", ret); \
+ transaction_free(&t); \
+ return ret; \
+ } \
+ \
+ fill_account_transaction_task_id(&t->request.account); \
+ fill_account_transaction_rem_addr(&t->request.account); \
+ \
+ return queue_transaction_for_bus_message(t, m); \
+}
+
+ACCOUNT_SEND_VARIANT(account_send, NULL);
+
+static int cmd_account_fill_handler(struct account_send_param *p,
+ sd_bus_message *m)
+{
+ return sd_bus_message_read_strv(m, &(p->command));
+}
+
+ACCOUNT_SEND_VARIANT(cmd_account_send, cmd_account_fill_handler);
+
+static int fill_authen_transaction_from_bus_msg(struct authen_send_param *p,
+ sd_bus_message *m)
+{
+ return sd_bus_message_read(m, AUTHEN_SEND_ARGS,
+ &p->user, &p->password,
+ &p->tty, &p->r_addr);
+}
+
+static int authen_send(sd_bus_message *m,
+ __unused void *userdata,
+ __unused sd_bus_error *error)
+{
+ struct transaction *t;
+ int ret;
+
+ syslog(LOG_DEBUG, "authen_send() call");
+
+ t = transaction_new(TRANSACTION_AUTHEN);
+ if (!t)
+ return -ENOMEM;
+
+ ret = fill_authen_transaction_from_bus_msg(&t->request.authen, m);
+ if (ret < 0) {
+ syslog(LOG_ERR, "Failed to parse authen_send() call: %d", ret);
+ transaction_free(&t);
+ return ret;
+ }
+
+ return queue_transaction_for_bus_message(t, m);
+}
+
+typedef int (*author_variant_fill_handler)(struct author_send_param *,
+ sd_bus_message *);
+
+static int fill_author_transaction_from_bus_msg(struct author_send_param *p,
+ sd_bus_message *m,
+ author_variant_fill_handler var_fn)
+{
+ int ret;
+ char *key = NULL, *value = NULL;
+
+ ret = sd_bus_message_read(m, BUS_TYPE_STRING BUS_TYPE_STRING BUS_TYPE_STRING,
+ &p->login, &p->tty, &p->r_addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY,
+ BUS_TYPE_STR_STR_DICT_ELEM);
+ if (ret < 0)
+ return ret;
+
+ while (sd_bus_message_read(m, BUS_TYPE_STR_STR_DICT_ELEM, &key, &value) > 0) {
+ if (strcmp("protocol", key) == 0) {
+ p->protocol = value;
+ }
+ else if (strcmp("service", key) == 0) {
+ p->service = value;
+ }
+ else if (strcmp("secrets", key) == 0) {
+ p->secrets = value;
+ }
+ else {
+ syslog(LOG_ERR, "Ignoring unsupported attribute-key: %s", key);
+ }
+ }
+
+ ret = sd_bus_message_exit_container(m);
+ if (ret < 0 || !var_fn)
+ return ret;
+
+ return var_fn(p, m);
+}
+
+#define AUTHOR_SEND_VARIANT(N, V) \
+static int N(sd_bus_message *m, \
+ __unused void *userdata, \
+ __unused sd_bus_error *error) \
+{ \
+ struct transaction *t; \
+ int ret; \
+ \
+ syslog(LOG_DEBUG, #N "() call"); \
+ \
+ t = transaction_new(TRANSACTION_AUTHOR); \
+ if (!t) \
+ return -ENOMEM; \
+ \
+ ret = fill_author_transaction_from_bus_msg(&t->request.author, m, V); \
+ if (ret < 0) { \
+ syslog(LOG_ERR, "Failed to parse " #N "() call: %d", ret); \
+ transaction_free(&t); \
+ return ret; \
+ } \
+ \
+ return queue_transaction_for_bus_message(t, m); \
+}
+
+AUTHOR_SEND_VARIANT(author_send, NULL);
+
+static int cmd_author_fill_handler(struct author_send_param *p,
+ sd_bus_message *m)
+{
+ return sd_bus_message_read_strv(m, &(p->cmd));
+}
+
+AUTHOR_SEND_VARIANT(cmd_author_send, cmd_author_fill_handler);
+
+int signal_offline_state_change() {
+ if (service->stop) {
+ syslog(LOG_ERR, "Unable to signal offline state change");
+ return -1;
+ }
+
+ pthread_mutex_lock(&service->bus_lock);
+ int ret = sd_bus_emit_properties_changed(
+ service->bus, TACPLUS_DAEMON_PATH, TACPLUS_DAEMON, "offline", NULL);
+ pthread_mutex_unlock(&service->bus_lock);
+
+ if (ret < 0)
+ syslog(LOG_ERR, "Failed to signal offline state change");
+ return ret;
+}
+
+static int offline_property_get(__unused sd_bus *bus,
+ __unused const char *path,
+ __unused const char *interface,
+ __unused const char *property,
+ sd_bus_message *reply,
+ __unused void *userdata,
+ __unused sd_bus_error *error)
+{
+ return sd_bus_message_append(reply, "b", connControl->state.offline);
+}
+
+static const sd_bus_vtable serv_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+ SD_BUS_METHOD("get_status", GET_STATUS_ARGS, GET_STATUS_RET,
+ get_status, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("account_send", ACCOUNT_SEND_ARGS, ACCOUNT_SEND_RET,
+ account_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("cmd_account_send", CMD_ACCOUNT_SEND_ARGS, CMD_ACCOUNT_SEND_RET,
+ cmd_account_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("authen_send", AUTHEN_SEND_ARGS, AUTHEN_SEND_RET,
+ authen_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("author_send", AUTHOR_SEND_ARGS, AUTHOR_SEND_RET,
+ author_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("cmd_author_send", CMD_AUTHOR_SEND_ARGS, CMD_AUTHOR_SEND_RET,
+ cmd_author_send, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_PROPERTY("offline", "b", offline_property_get, 0,
+ SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_VTABLE_END
+};
+
+static void dbus_service_fail(tacplus_dbus_service_t service)
+{
+ service->stop = true;
+ if (kill(getpid(), SIGTERM) != 0)
+ abort();
+}
+
+static void *dbus_service_listen(__unused void *arg)
+{
+ int ret;
+
+ while (! service->stop) {
+ pthread_mutex_lock(&service->bus_lock);
+ ret = sd_bus_process(service->bus, NULL);
+ pthread_mutex_unlock(&service->bus_lock);
+
+ if (ret > 0) /* More queued messages to process */
+ continue;
+ if (ret < 0)
+ goto fail;
+
+ ret = sd_bus_wait(service->bus, 1000000);
+ if (ret < 0)
+ goto fail;
+ }
+
+ syslog(LOG_DEBUG, "Stopping dbus_service_listen thread");
+ return NULL;
+
+fail:
+ syslog(LOG_ERR, "DBus processing error: %s (%d)", strerror(-ret), ret);
+ dbus_service_fail(service);
+ return NULL;
+}
+
+static void force_wake_queue_threads(tacplus_dbus_service_t service)
+{
+ assert(service->stop || !service->process);
+
+ /* Wake up queue threads - transaction type MUST be TRANSACTION_INVALID */
+
+ enqueue(service->tacacs_query_q, transaction_new(TRANSACTION_INVALID));
+ enqueue(service->tacacs_response_q, transaction_new(TRANSACTION_INVALID));
+}
+
+static void start_processing(tacplus_dbus_service_t service)
+{
+ assert(! service->stop);
+
+ if (service->process) {
+ syslog(LOG_DEBUG, "Processing already started");
+ return;
+ }
+
+ /*
+ * The process flag MUST be set before starting the consumer threads
+ * otherwise they may immediately exit
+ */
+ service->process = true;
+
+ if (pthread_create(&service->request_thread, NULL,
+ consume_dbus_req_thread, NULL)) {
+ syslog(LOG_ERR, "Failed to instantiate request_thread");
+ service->process = false;
+ return;
+ }
+
+ if (pthread_create(&service->reply_thread, NULL,
+ consume_dbus_reply_thread, NULL)) {
+ syslog(LOG_ERR, "Failed to instantiate reply_thread");
+ service->process = false;
+ force_wake_queue_threads(service);
+ pthread_join(service->request_thread, NULL);
+ return;
+ }
+}
+
+static void stop_processing(tacplus_dbus_service_t service)
+{
+ /* Check the processing threads have started */
+ if (!service->process)
+ return;
+
+ service->process = false;
+ force_wake_queue_threads(service);
+
+ syslog(LOG_DEBUG, "Waiting on request_thread...");
+ pthread_join(service->request_thread, NULL);
+
+ syslog(LOG_DEBUG, "Waiting on reply_thread...");
+ pthread_join(service->reply_thread, NULL);
+}
+
+void dbus_service_wait(void)
+{
+ syslog(LOG_DEBUG, "Waiting on dbus_thread...");
+ pthread_join(service->dbus_thread, NULL);
+}
+
+// Calling thread must hold service->bus_lock
+static void _dbus_service_start_fail_cleanup()
+{
+ if (service->bus)
+ sd_bus_unref(service->bus);
+ service->bus = NULL;
+ pthread_mutex_unlock(&service->bus_lock);
+}
+
+int dbus_service_start()
+{
+ int ret;
+
+ if (service->bus || !service->stop) {
+ syslog(LOG_DEBUG, "DBus service is already running");
+ return 0;
+ }
+
+ pthread_mutex_lock(&service->bus_lock);
+
+ ret = sd_bus_open_system(&service->bus);
+ if (ret < 0) {
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ ret = sd_bus_request_name(service->bus, TACPLUS_DAEMON, 0);
+ if (ret < 0) {
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ ret = sd_bus_add_object_vtable(service->bus, NULL, TACPLUS_DAEMON_PATH,
+ TACPLUS_DAEMON, serv_vtable, NULL);
+ if (ret < 0) {
+ sd_bus_release_name(service->bus, TACPLUS_DAEMON);
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ service->stop = false;
+
+ ret = pthread_create(&service->dbus_thread, NULL, dbus_service_listen, NULL);
+ if (ret != 0) {
+ syslog(LOG_ERR, "Failed to instantiate dbus_thread: %s", strerror(ret));
+ sd_bus_release_name(service->bus, TACPLUS_DAEMON);
+ _dbus_service_start_fail_cleanup();
+ return ret;
+ }
+
+ start_processing(service);
+
+ /* TODO: add dbus filters? */
+ pthread_mutex_unlock(&service->bus_lock);
+ return service->process ? 0 : -1;
+}
+
+void dbus_service_stop(void)
+{
+ service->stop = true;
+ stop_processing(service);
+}
+
+void dbus_service_pause()
+{
+ /*
+ * Stop request/reply consumer threads and acquire the bus lock.
+ * Acquiring the bus lock is required to prevent the processing of
+ * DBus requests.
+ */
+ stop_processing(service);
+ pthread_mutex_lock(&service->bus_lock);
+}
+
+int dbus_service_resume()
+{
+ /*
+ * Start the request/reply consumer threads and release the bus lock
+ * so we start processing DBus requests once again.
+ */
+ start_processing(service);
+ pthread_mutex_unlock(&service->bus_lock);
+ return service->process ? 0 : -1;
+}
+
+bool dbus_service_failed()
+{
+ return (service->stop && service->process);
+}
diff --git a/tacplus-daemon/dbus_service.h b/tacplus-daemon/dbus_service.h
new file mode 100644
index 0000000..09eb24e
--- /dev/null
+++ b/tacplus-daemon/dbus_service.h
@@ -0,0 +1,36 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019, AT&T Intellectual Property.
+ Copyright (c) 2015 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#ifndef _DBUS_SERVICE_H
+#define _DBUS_SERVICE_H
+
+#include <assert.h>
+#include <libtac.h>
+#include <tacplus.h>
+#include "tacplus_srv_conn.h"
+#include "utils.h"
+
+typedef struct tacplus_dbus_service * tacplus_dbus_service_t;
+
+#define TACPLUS_DAEMON "net.vyatta.tacplus"
+#define TACPLUS_DAEMON_PATH "/net/vyatta/tacplus"
+
+/* prototypes */
+extern void dbus_service_init();
+extern void dbus_service_deinit();
+extern int dbus_service_start();
+extern void dbus_service_stop();
+extern void dbus_service_pause();
+extern int dbus_service_resume();
+extern void dbus_service_wait();
+bool dbus_service_failed();
+
+int signal_offline_state_change();
+
+#endif /*_DBUS_SERVICE_H */
diff --git a/tacplus-daemon/global.c b/tacplus-daemon/global.c
new file mode 100644
index 0000000..1de9aeb
--- /dev/null
+++ b/tacplus-daemon/global.c
@@ -0,0 +1,106 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019, AT&T Intellectual Property
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <syslog.h>
+
+#include "dbus_service.h"
+#include "global.h"
+#include "utils.h"
+
+static void offline_timer_expiry_cb(__attribute__((unused)) union sigval val)
+{
+ int id = val.sival_int;
+
+ syslog(LOG_DEBUG, "Offline timer %d expired", id);
+
+ pthread_mutex_lock(&connControl->state.lock);
+
+ /*
+ * Every time we attempt to start an offline timer we increment the
+ * offline_timer_id. The incremented value is stored ready to be passed
+ * into the callback (ie. us) at timer expiry.
+ *
+ * This allows us to detect a race condition where we (attempted to)
+ * cancel a timer just after it expired and ignore the callback.
+ */
+ if (id != connControl->state.offline_timer_id) {
+ pthread_mutex_unlock(&connControl->state.lock);
+ syslog(LOG_DEBUG, "Stale timer - ignoring");
+ return;
+ }
+
+ if (! connControl->state.offline) {
+ pthread_mutex_unlock(&connControl->state.lock);
+ syslog(LOG_DEBUG, "Already online");
+ return;
+ }
+
+ connControl->state.offline = false;
+ connControl->state.offline_timer_id = 0;
+ signal_offline_state_change();
+
+ pthread_mutex_unlock(&connControl->state.lock);
+
+ syslog(LOG_NOTICE, "TACACS+ component back online");
+}
+
+bool tacplusd_go_online() {
+ pthread_mutex_lock(&connControl->state.lock);
+
+ if (! connControl->state.offline) {
+ pthread_mutex_unlock(&connControl->state.lock);
+ return true;
+ }
+
+ /* Expire the running timer to trigger an online state change */
+ int ret = expire_timer(connControl->state.offline_timer);
+
+ pthread_mutex_unlock(&connControl->state.lock);
+ return ret == 0;
+}
+
+bool tacplusd_go_offline(const struct timespec *time) {
+ struct itimerspec it = { .it_value = *time };
+
+ pthread_mutex_lock(&connControl->state.lock);
+
+ /* If we are already offline then cancel an existing timer */
+ if (connControl->state.offline)
+ timer_delete(connControl->state.offline_timer);
+
+ /* Start the offline timer */
+ union sigval sv = { .sival_int = ++connControl->state.offline_timer_id };
+ if (new_cb_timer(&connControl->state.offline_timer,
+ offline_timer_expiry_cb, &sv) < 0) {
+ pthread_mutex_unlock(&connControl->state.lock);
+ return false;
+ }
+
+ if (set_timer(connControl->state.offline_timer, &it) < 0) {
+ pthread_mutex_unlock(&connControl->state.lock);
+ return false;
+ }
+
+ if (connControl->state.offline) {
+ pthread_mutex_unlock(&connControl->state.lock);
+ return true;
+ }
+
+ /* If we were previously online then signal a state change */
+ connControl->state.offline = true;
+ signal_offline_state_change();
+
+ pthread_mutex_unlock(&connControl->state.lock);
+
+ syslog(LOG_WARNING, "TACACS+ component has gone offline");
+ return true;
+}
+
+bool tacplusd_online() {
+ return !connControl->state.offline;
+}
diff --git a/tacplus-daemon/global.h b/tacplus-daemon/global.h
new file mode 100644
index 0000000..d7bb592
--- /dev/null
+++ b/tacplus-daemon/global.h
@@ -0,0 +1,38 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019, AT&T Intellectual Property
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+struct tacplus_global_state {
+ /* Lock to be held while manipulating global state */
+ pthread_mutex_t lock;
+
+ /* ID of the offline expiry timer returned from timer_create() */
+ timer_t offline_timer;
+
+ /* Internal timer ID used to detect expiry races */
+ uint8_t offline_timer_id;
+
+ /* Flag indicating whether the component is offline */
+ bool offline;
+};
+
+typedef struct {
+ struct tacplus_options *opts;
+ struct tacplus_global_state state;
+} ConnectionControl;
+
+extern ConnectionControl *connControl;
+
+bool tacplusd_go_online();
+bool tacplusd_go_offline(const struct timespec *);
+bool tacplusd_online();
diff --git a/tacplus-daemon/main.c b/tacplus-daemon/main.c
new file mode 100644
index 0000000..46de2ba
--- /dev/null
+++ b/tacplus-daemon/main.c
@@ -0,0 +1,192 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019, AT&T Intellectual Property.
+ Copyright (c) 2015 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <pthread.h>
+
+#include "global.h"
+#include "main.h"
+#include "statistics.h"
+#include "tacplus_srv_conn.h"
+
+/*
+ * TODO
+ * add license to all source files
+ */
+
+static int run = 1; /* Initially to enter while loop */
+static int reload = 0;
+
+static ConnectionControl _connControl = { .state = { .offline = false } };
+ConnectionControl *connControl = &_connControl;
+
+static void signal_wait(sigset_t *set)
+{
+ int s, sig;
+
+ for (;;) {
+ s = sigwait(set, &sig);
+ if (s != 0) {
+ syslog(LOG_ERR, "Signal handler sigwait() call returned error");
+ }
+ syslog(LOG_DEBUG, "Signal handler received: %d\n", sig);
+ switch(sig) {
+ case SIGTERM:
+ syslog(LOG_DEBUG, "Signal handler caught SIGTERM");
+ run = 0;
+ return;
+
+ case SIGHUP:
+ syslog(LOG_DEBUG, "Signal handler caught SIGHUP");
+ reload = 1;
+ return;
+
+ default:
+ syslog(LOG_DEBUG, "Ignoring signal %d", sig);
+ break;
+ }
+ }
+}
+
+static int setup_service(const char *tacplus_cfg)
+{
+ int ret = 0;
+
+ /* enable read timeout handling */
+ tac_enable_readtimeout(1);
+
+#ifndef HAVE_LIBTAC_EVENT
+ /* libtac by default uses PAP auth method. tac_login is a global/extern variable of libtac */
+ strncpy(tac_login, "login", 5);
+#endif
+
+ connControl->opts = tacplus_parse_reload_options(tacplus_cfg,
+ &connControl->opts);
+ if(connControl->opts == NULL) {
+ syslog(LOG_NOTICE, "No valid configuration");
+ ret = -1;
+ goto done;
+ }
+
+ /* Set global DSCP marking in libtac library */
+ tac_set_dscp(connControl->opts->dscp);
+
+ syslog(LOG_INFO, "Configuration loaded successfully");
+
+ ret = create_statistics(connControl->opts->n_servers);
+ if(ret != 0) {
+ syslog(LOG_ERR, "Failed to allocate statistics");
+ goto done;
+ }
+ syslog(LOG_DEBUG, "Statistics allocated successfully");
+
+done:
+ return ret;
+}
+
+static int reload_service(const char *tacplus_cfg)
+{
+ int ret;
+
+ reload = 0;
+ syslog(LOG_NOTICE, "Reloading");
+ dbus_service_pause();
+
+ free_statistics();
+ ret = setup_service(tacplus_cfg);
+
+ ret |= dbus_service_resume();
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = 0;
+ sigset_t set;
+ char *tacplus_cfg;
+ char *tacplus_pid;
+
+ if (argc == 3) {
+ tacplus_cfg = argv[1];
+ tacplus_pid = argv[2];
+ }
+ else {
+ fprintf(stderr, "Insufficient arguments to the daemon\n");
+ return -1;
+ }
+
+ if (getenv("DEBUG"))
+ tac_debug_enable = 1;
+
+ if (!getenv("NODAEMON")) {
+ openlog("tacplusd", LOG_ODELAY, LOG_AUTH);
+ daemonize(tacplus_pid);
+ }
+ else
+ openlog("tacplusd", LOG_PERROR, LOG_AUTH);
+
+ /* from this point onwards, we're the child */
+ syslog(LOG_NOTICE, "Tacplusd daemonized successfully");
+ syslog(LOG_DEBUG, "Tacplusd started with %s\n", tacplus_cfg);
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGQUIT);
+ sigaddset(&set, SIGKILL);
+ sigaddset(&set, SIGUSR1);
+ sigaddset(&set, SIGUSR2);
+ sigaddset(&set, SIGTERM);
+ sigaddset(&set, SIGHUP);
+
+ ret = pthread_sigmask(SIG_BLOCK, &set, NULL);
+
+ if (ret != 0) {
+ syslog(LOG_ERR, "Blocking signals failed");
+ goto done;
+ }
+
+ pthread_mutex_init(&connControl->state.lock, NULL);
+
+ dbus_service_init();
+
+ if (setup_service(tacplus_cfg) != 0) {
+ ret = -1;
+ goto done;
+ }
+
+ if (dbus_service_start() < 0) {
+ syslog(LOG_ERR, "Failed to start DBus service");
+ ret = -1;
+ goto done;
+ }
+ syslog(LOG_DEBUG, "DBus service setup successful");
+
+ /* Send an offline state change signal to indicate we have started up */
+ signal_offline_state_change();
+
+ while (run) {
+ if (reload && (ret = reload_service(tacplus_cfg)) != 0)
+ goto done;
+
+ signal_wait(&set);
+
+ ret = dbus_service_failed() ? 1 : 0;
+ }
+
+done:
+ syslog(LOG_NOTICE, "Stopping");
+
+ dbus_service_stop();
+ dbus_service_wait();
+ dbus_service_deinit();
+
+ free_statistics();
+ cleanup_tacplus_options(&connControl->opts);
+
+ syslog(LOG_NOTICE, "Shutting down");
+ return ret;
+}
diff --git a/tacplus-daemon/main.h b/tacplus-daemon/main.h
new file mode 100644
index 0000000..10e56d3
--- /dev/null
+++ b/tacplus-daemon/main.h
@@ -0,0 +1,12 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2015 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "daemon.h"
+#include "dbus_service.h"
+#include <signal.h>
diff --git a/tacplus-daemon/net.vyatta.tacplus.conf b/tacplus-daemon/net.vyatta.tacplus.conf
new file mode 100644
index 0000000..2ee230c
--- /dev/null
+++ b/tacplus-daemon/net.vyatta.tacplus.conf
@@ -0,0 +1,9 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="net.vyatta.tacplus"/>
+ <allow send_destination="net.vyatta.tacplus"/>
+ </policy>
+</busconfig>
diff --git a/tacplus-daemon/parser.c b/tacplus-daemon/parser.c
new file mode 100644
index 0000000..5684769
--- /dev/null
+++ b/tacplus-daemon/parser.c
@@ -0,0 +1,317 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <assert.h>
+#include <glib.h>
+#include <netinet/ip.h>
+#include <stdbool.h>
+
+#include "parser.h"
+#include "utils.h"
+
+/* TODO: set max config size
+ * header file
+ * make more versatile/flexible
+ */
+
+static const char *s_general = "general";
+static const char *s_options = "options";
+
+static inline
+void g_syslog(int priority, const char *fmt, GError *e)
+{
+ syslog(priority, fmt, e->message);
+ g_error_free(e);
+}
+
+static inline
+void sa_set_port(struct sockaddr *sa, ushort port)
+{
+ if (!sa) {
+ syslog(LOG_CRIT, "sa should never be NULL");
+ exit(1);
+ }
+
+ if (sa->sa_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *) sa;
+ sin->sin_port = htons(port);
+ } else if (sa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
+ sin6->sin6_port = htons(port);
+ }
+}
+
+static
+bool isReservedSection(const char *name)
+{
+ return (!strcmp(name, s_general) || !strcmp(name, s_options));
+}
+
+void read_config(const char *f_name, struct tacplus_options **opts)
+{
+ GKeyFile *keyfile;
+ GKeyFileFlags flags;
+ GError *error = NULL;
+ gsize length;
+ const struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ .ai_flags = AI_NUMERICHOST,
+ };
+ struct connection conn[TACPLUS_MAX_SERVERS];
+ unsigned nservers, setupTimeout;
+ int dscp;
+ unsigned i, j;
+
+ nservers = 0;
+
+ /* initialize key file */
+ keyfile = g_key_file_new();
+ flags = G_KEY_FILE_NONE;
+
+ if (!g_key_file_load_from_file(keyfile, f_name, flags, &error)) {
+ g_syslog(LOG_ERR, "config file open: %s", error);
+ g_key_file_free(keyfile);
+ return;
+ }
+
+ /* get the names of sections; any section that doesn't have
+ * a reserved name is assumed to be a server configuration.
+ */
+
+ char **sections = g_key_file_get_groups(keyfile, &length);
+
+ /* any global options would be handled here from the
+ * 'general' section
+ */
+
+ gboolean broadcast = g_key_file_get_boolean(keyfile, s_general, "BroadcastAccounting", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse BroadcastAccounting option: %s", error);
+ goto cleanup2;
+ }
+
+#ifndef HAVE_LIBTAC_EVENT
+ if (broadcast) {
+ syslog(LOG_ERR, "Cannot enable unsupported BroadcastAccounting option");
+ broadcast = false;
+ }
+#endif
+
+ setupTimeout = g_key_file_get_integer(keyfile, s_general, "SetupTimeout", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse SetupTimeout option: %s", error);
+ goto cleanup2;
+ }
+ if (setupTimeout <= 0) {
+ syslog(LOG_ERR, "parse SetupTimeout option must be higher than 0.\n");
+ goto cleanup2;
+ }
+
+ dscp = g_key_file_get_integer(keyfile, s_general, "Dscp", &error);
+ if (error) {
+ if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
+ g_syslog(LOG_ERR, "parse Dscp option: %s", error);
+ goto cleanup2;
+ }
+ dscp = IPTOS_CLASS_CS6;
+ g_error_free(error);
+ } else if (dscp < 0 || dscp > 63) {
+ syslog(LOG_ERR, "Dscp value must be in the range <0-63>");
+ goto cleanup2;
+ } else {
+ // The two least significant bits are used for ECN
+ dscp <<= 2;
+ }
+
+ for (i = j = 0; i < length; ++i) {
+ int hold_down, port, timeout, err;
+ gchar *gsecret, *addr, *src_addr, *gsrc_intf;
+ struct addrinfo *ai, *pai, *sai;
+ gchar *server = sections[i];
+ const char *secret, *src_intf;
+ bool valid = true;
+
+ if (isReservedSection(server))
+ continue;
+
+ /* Clear variables which may be set from previous iterations */
+ error = NULL;
+ ai = pai = sai = NULL;
+
+ /* start with tuple of address and port */
+ addr = g_key_file_get_string(keyfile, server, "Address", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse server address: %s", error);
+ ai = NULL;
+ valid = false;
+ } else {
+ /* ignore the port, we'll patch it in later... */
+ err = getaddrinfo(addr, NULL, &hints, &ai);
+ if (err != 0) {
+ syslog(LOG_ERR, "parse server address: %s",
+ gai_strerror(err));
+ valid = false;
+ }
+ }
+
+ port = g_key_file_get_integer(keyfile, server, "Port", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse port: %s", error);
+ valid = false;
+ }
+
+ /* ... then required secret and timeout. */
+ gsecret = g_key_file_get_string(keyfile, server, "Secret", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse secret: %s", error);
+ valid = false;
+ secret = NULL;
+ } else {
+ /* we do this so that the special memory destruction
+ * that glib requires is confined to this module.
+ */
+ secret = strdup(gsecret);
+ if (! secret) {
+ syslog(LOG_CRIT, "tacplus secret allocation fail: out-of-memory");
+ valid = false;
+ }
+ g_free(gsecret);
+ }
+
+ timeout = g_key_file_get_integer(keyfile, server, "Timeout", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse timeout: %s", error);
+ valid = false;
+ }
+
+ hold_down = g_key_file_get_integer(keyfile, server, "HoldDown", &error);
+ if (error) {
+ g_syslog(LOG_ERR, "parse HoldDown: %s", error);
+ valid = false;
+ } else if (hold_down < 0) {
+ syslog(LOG_ERR, "Invalid negative HoldDown %d", hold_down);
+ valid = false;
+ }
+
+ /* substitute in port # */
+ for (pai = ai; pai != NULL; pai = pai->ai_next)
+ /* if pai is non-NULL, then pai->ai_addr should
+ * also never be NULL...
+ */
+ sa_set_port(pai->ai_addr, port);
+
+ /* SourceAddress, which is optional... */
+ src_addr = g_key_file_get_string(keyfile, server, "SourceAddress", &error);
+ if (error) {
+ if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND)
+ g_syslog(LOG_ERR, "parse source address: %s",
+ error);
+ else
+ g_error_free(error);
+
+ sai = NULL;
+ } else {
+ err = getaddrinfo(src_addr, "", &hints, &sai);
+ if (err != 0) {
+ syslog(LOG_ERR, "parse source address: %s",
+ gai_strerror(err));
+ valid = false;
+ }
+ }
+
+ /* Optional SourceInterface */
+ error = NULL;
+ gsrc_intf = g_key_file_get_string(keyfile, server, "SourceInterface", &error);
+ if (error) {
+ if (error->code != G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
+ g_syslog(LOG_ERR, "parse source interface: %s", error);
+ valid = false;
+ }
+ else
+ g_error_free(error);
+ src_intf = NULL;
+ } else {
+ src_intf = strdup (gsrc_intf);
+ if (! src_intf) {
+ syslog(LOG_CRIT, "tacplus source interface allocation "
+ "fail: out-of-memory");
+ valid = false;
+ }
+ g_free(gsrc_intf);
+ }
+
+ if (!valid)
+ goto cleanup;
+
+ if (j == TACPLUS_MAX_SERVERS) {
+ syslog(LOG_WARNING,
+ "too many servers configured: ignoring %s", server);
+ goto cleanup;
+ }
+
+ conn[j].addr = ai;
+ conn[j].secret = secret;
+ conn[j].timeout = timeout;
+ conn[j].hold_down = hold_down;
+ conn[j].src_addr = sai;
+ conn[j].src_intf = src_intf;
+ ++j;
+
+cleanup:
+ g_free((char *)addr);
+ g_free((char *)src_addr);
+
+ if (valid)
+ continue;
+
+ if (ai)
+ freeaddrinfo(ai);
+ if (sai)
+ freeaddrinfo(sai);
+ }
+
+ nservers = j;
+
+cleanup2:
+ /* release stuff... */
+ g_strfreev(sections);
+ g_key_file_free(keyfile);
+
+ if (!nservers) {
+ syslog(LOG_ERR, "no servers configured");
+ exit(1);
+ }
+
+ *opts = tacplus_options_alloc(nservers);
+ if (!opts) {
+ syslog(LOG_CRIT, "tacplus_options allocation fail: out-of-memory");
+ exit(1);
+ }
+
+ (*opts)->next_server = INVALID_SERVER_ID;
+ (*opts)->curr_server = 0;
+ (*opts)->broadcast = broadcast;
+ (*opts)->dscp = dscp;
+ (*opts)->setupTimeout = setupTimeout;
+
+ for (i = 0; i < nservers; ++i) {
+ (*opts)->server[i].id = i;
+ (*opts)->server[i].addrs = conn[i].addr;
+ (*opts)->server[i].src_addrs = conn[i].src_addr;
+ (*opts)->server[i].src_intf = conn[i].src_intf;
+ (*opts)->server[i].secret = conn[i].secret;
+ (*opts)->server[i].timeout = conn[i].timeout;
+ (*opts)->server[i].hold_down = conn[i].hold_down;
+#ifdef HAVE_LIBTAC_EVENT
+ (*opts)->server[i].session = NULL;
+#endif
+ }
+}
+
diff --git a/tacplus-daemon/parser.h b/tacplus-daemon/parser.h
new file mode 100644
index 0000000..cdfa417
--- /dev/null
+++ b/tacplus-daemon/parser.h
@@ -0,0 +1,30 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include "tacplus_srv_conn.h"
+#include <syslog.h>
+
+#define TACPLUS_MAX_SERVERS 100
+
+struct connection {
+ struct addrinfo *addr;
+ const char *secret;
+ int timeout;
+ unsigned hold_down;
+ struct addrinfo *src_addr;
+ const char *src_intf;
+};
+
+void read_config(const char *, struct tacplus_options **);
diff --git a/tacplus-daemon/queue.c b/tacplus-daemon/queue.c
new file mode 100644
index 0000000..fae583f
--- /dev/null
+++ b/tacplus-daemon/queue.c
@@ -0,0 +1,127 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "queue.h"
+
+Queue * create_queue(void (*free_element)(void *))
+{
+ Queue *q;
+ q = malloc(sizeof(*q));
+
+ if (q == NULL) {
+ syslog(LOG_ERR, "Failed to allocate queue");
+ }
+ else {
+ q->front = NULL;
+ q->rear = NULL;
+ q->free_element = free_element;
+ pthread_mutex_init(&(q->lock), NULL);
+ pthread_cond_init(&(q->empty), NULL);
+ }
+ return q;
+}
+
+void enqueue(Queue *q, void *e)
+{
+ Node *n;
+
+ if (q == NULL || e == NULL)
+ return;
+
+ pthread_mutex_lock(&(q->lock));
+
+ n = malloc(sizeof(*n));
+ n->element = e;
+ n->next = NULL;
+
+ if (is_queue_empty(q)) {
+ q->front = n;
+ q->rear = n;
+ }
+ else {
+ q->rear->next = n;
+ q->rear = n;
+ }
+ pthread_cond_broadcast(&(q->empty));
+ pthread_mutex_unlock(&(q->lock));
+}
+
+void re_enqueue(Queue *q, void *e)
+{
+ Node *n;
+
+ if (q == NULL || e == NULL)
+ return;
+
+ pthread_mutex_lock(&(q->lock));
+
+ n = malloc(sizeof(*n));
+ n->element = e;
+ n->next = q->front;
+
+ if (is_queue_empty(q)) {
+ q->front = n;
+ q->rear = n;
+ }
+ else {
+ q->front = n;
+ }
+ pthread_cond_broadcast(&(q->empty));
+ pthread_mutex_unlock(&(q->lock));
+}
+
+void * dequeue(Queue *q)
+{
+ Node *n, *nextNode;
+ void *data = NULL;
+
+ pthread_mutex_lock(&(q->lock));
+
+ if (!is_queue_empty(q)) {
+ n = q->front;
+ nextNode = q->front->next;
+ if (q->front == q->rear) {
+ q->rear = nextNode;
+ }
+ q->front = nextNode;
+ data = n->element;
+ free(n);
+ }
+ pthread_cond_broadcast(&(q->empty));
+ pthread_mutex_unlock(&(q->lock));
+ return data;
+}
+
+int is_queue_empty(Queue *q)
+{
+ return (q->front == NULL);
+}
+
+void destroy_queue(Queue **q)
+{
+ Node *n = NULL;
+ Node *next_n;
+
+ if (!q || !*q)
+ return;
+
+ for (n = (*q)->front; n != NULL ; n = next_n) {
+ next_n = n->next;
+ if((*q)->free_element)
+ (*q)->free_element(n->element);
+ free(n);
+ }
+
+ pthread_mutex_destroy(&((*q)->lock));
+ pthread_cond_destroy(&((*q)->empty));
+
+ free(*q);
+ *q = NULL;
+}
+
diff --git a/tacplus-daemon/queue.h b/tacplus-daemon/queue.h
new file mode 100644
index 0000000..6220912
--- /dev/null
+++ b/tacplus-daemon/queue.h
@@ -0,0 +1,38 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#ifndef QUEUE_H
+#define QUEUE_H
+
+#include <syslog.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <pthread.h>
+
+typedef struct queueNode {
+ void *element;
+ struct queueNode *next;
+} Node;
+
+typedef struct queue {
+ Node *front, *rear;
+ pthread_mutex_t lock;
+ pthread_cond_t empty;
+ void (*free_element)(void *);
+} Queue;
+
+extern Queue * create_queue(void (*)(void *));
+extern void enqueue(Queue *q, void *);
+extern void re_enqueue(Queue *q, void *e);
+extern int is_queue_empty(Queue *);
+extern void * dequeue(Queue *);
+extern void destroy_queue(Queue **);
+
+#endif /* QUEUE_H */
diff --git a/tacplus-daemon/statistics.c b/tacplus-daemon/statistics.c
new file mode 100644
index 0000000..c644aca
--- /dev/null
+++ b/tacplus-daemon/statistics.c
@@ -0,0 +1,134 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "statistics.h"
+
+static struct statistics **stats;
+static int created = 0;
+
+int create_statistics(int n)
+{
+ int i;
+ int ret = 0;
+
+ if(!created) {
+ stats = malloc(sizeof(*stats) * n);
+
+ if(!stats)
+ return -1;
+
+ for(i=0; i<n ; i++) {
+ stats[i] = calloc(1, sizeof(**stats));
+ if(!stats[i]) {
+ free_statistics();
+ return -1;
+ }
+ created++;
+ }
+ }
+
+ return ret;
+}
+
+void inc_authen_requests(int i)
+{
+ stats[i]->authen_requests++;
+}
+
+void inc_authen_replies(int i)
+{
+ stats[i]->authen_replies++;
+}
+
+void inc_author_requests(int i)
+{
+ stats[i]->author_requests++;
+}
+
+void inc_author_replies(int i)
+{
+ stats[i]->author_replies++;
+}
+
+void inc_acct_requests(int i)
+{
+ stats[i]->acct_requests++;
+}
+
+void inc_acct_replies(int i)
+{
+ stats[i]->acct_replies++;
+}
+
+void inc_unknown_replies(int i)
+{
+ stats[i]->unknown_replies++;
+}
+
+void inc_failed_connects(int i)
+{
+ stats[i]->failed_connects++;
+}
+
+int get_authen_requests(int i)
+{
+ return stats[i]->authen_requests;
+}
+
+int get_authen_replies(int i)
+{
+ return stats[i]->authen_replies;
+}
+
+int get_author_requests(int i)
+{
+ return stats[i]->author_requests;
+}
+
+int get_author_replies(int i)
+{
+ return stats[i]->author_replies;
+}
+
+int get_acct_requests(int i)
+{
+ return stats[i]->acct_requests;
+}
+
+int get_acct_replies(int i)
+{
+ return stats[i]->acct_replies;
+}
+
+int get_failed_connects(int i)
+{
+ return stats[i]->failed_connects;
+}
+
+int get_unknown_replies(int i)
+{
+ return stats[i]->unknown_replies;
+}
+
+void free_statistics()
+{
+ int i;
+
+ if (created) {
+ for(i=0; i<created ; i++) {
+ free(stats[i]);
+ }
+ }
+
+ free(stats);
+
+ stats = NULL;
+
+ created = 0;
+}
diff --git a/tacplus-daemon/statistics.h b/tacplus-daemon/statistics.h
new file mode 100644
index 0000000..ce722a3
--- /dev/null
+++ b/tacplus-daemon/statistics.h
@@ -0,0 +1,50 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <stdlib.h>
+
+struct statistics {
+ int authen_requests;
+ int authen_replies;
+ int author_requests;
+ int author_replies;
+ int acct_requests;
+ int acct_replies;
+ int failed_connects;
+ int unknown_replies;
+};
+
+
+extern int create_statistics(int);
+extern void free_statistics();
+
+/* Authentication stats */
+extern void inc_authen_requests(int);
+extern void inc_authen_replies(int);
+extern int get_authen_requests(int);
+extern int get_authen_replies(int);
+
+/* Authorization stats */
+extern void inc_author_requests(int);
+extern void inc_author_replies(int);
+extern int get_author_requests(int);
+extern int get_author_replies(int);
+
+/* Accounting stats */
+extern void inc_acct_requests(int);
+extern void inc_acct_replies(int);
+extern int get_acct_requests(int);
+extern int get_acct_replies(int);
+
+/* Misc stats */
+extern void inc_failed_connects(int);
+extern int get_failed_connects(int);
+
+extern void inc_unknown_replies(int);
+extern int get_unknown_replies(int);
diff --git a/tacplus-daemon/tacplus_srv_conn.c b/tacplus-daemon/tacplus_srv_conn.c
new file mode 100644
index 0000000..6da9f48
--- /dev/null
+++ b/tacplus-daemon/tacplus_srv_conn.c
@@ -0,0 +1,595 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "global.h"
+#include "tacplus_srv_conn.h"
+#include "dbus_service.h"
+#include "statistics.h"
+#include "parser.h"
+
+
+
+static bool
+tacplus_server_remaining_hold_down_at(const struct tacplus_options_server *server,
+ const struct timespec *cur_time,
+ struct timespec *remaining)
+{
+ struct timespec expires;
+
+ if (remaining)
+ SET_TIMESPEC_VALS(*remaining, 0, 0);
+
+ /* Hold down disabled */
+ if (server->hold_down == 0)
+ return false;
+
+ /* No trouble seen */
+ if (TIMESPEC_VALS_EQ(server->state.lastTrouble, -1, -1))
+ return false;
+
+ /* Clock shift - not much to do other than expire the timer */
+ if (timespec_cmp(cur_time, &server->state.lastTrouble) < 0)
+ return false;
+
+ expires = server->state.lastTrouble;
+ expires.tv_sec += server->hold_down;
+
+ if (timespec_cmp(cur_time, &expires) >= 0)
+ return false;
+
+ if (remaining)
+ timespec_sub(&expires, cur_time, remaining);
+
+ return true;
+}
+
+bool
+tacplus_server_remaining_hold_down(const struct tacplus_options_server *server,
+ struct timespec *remaining)
+{
+ struct timespec cur_time;
+
+ cur_mono_time(&cur_time);
+ return tacplus_server_remaining_hold_down_at(server, &cur_time, remaining);
+}
+
+time_t
+tacplus_server_remaining_hold_down_secs(const struct tacplus_options_server *server)
+{
+ struct timespec remaining;
+
+ tacplus_server_remaining_hold_down(server, &remaining);
+
+ /* Round to nearest second */
+ if (remaining.tv_nsec >= SEC_TO_NSECS/2)
+ remaining.tv_sec++;
+
+ return remaining.tv_sec;
+}
+
+bool
+tacplus_server_is_held_down(const struct tacplus_options_server *server)
+{
+ return tacplus_server_remaining_hold_down(server, NULL);
+}
+
+void
+tacplus_server_activate_hold_down(struct tacplus_options_server *server)
+{
+ if (server->hold_down) {
+ char *addr_str = addrinfo_to_string(server->addrs);
+
+ syslog(LOG_DEBUG, "Hold down timer started on %s for %us",
+ strOrNil(addr_str), server->hold_down);
+ free(addr_str);
+ }
+
+ cur_mono_time(&server->state.lastTrouble);
+}
+
+void
+tacplus_server_reset_hold_down(struct tacplus_options_server *server)
+{
+ SET_TIMESPEC_VALS(server->state.lastTrouble, -1, -1);
+}
+
+static
+bool tacplus_connect_ith(
+#ifdef HAVE_LIBTAC_EVENT
+ struct tac_session *sess,
+#endif
+ unsigned i)
+{
+ struct tacplus_options *opts = connControl->opts;
+ struct tacplus_options_server *server = tacplus_server(opts, i);
+ int timeout;
+ char *dest_addr_str = NULL;
+ char *src_addr_str = NULL;
+ struct addrinfo *addr, *src_addr = NULL;
+ bool success = false;
+
+ addr = server->addrs;
+
+ dest_addr_str = addrinfo_to_string(addr);
+
+ if (server->src_addrs)
+ src_addr = server->src_addrs;
+ else if (server->src_intf) {
+ int if_up = is_interface_up(server->src_intf);
+ switch (if_up) {
+ case 1:
+ break;
+ case 0:
+ syslog(LOG_DEBUG, "Source interface %s is not up",
+ server->src_intf);
+ /* fall through */
+ default:
+ goto fail;
+ }
+
+ src_addr = get_interface_addrinfo(server->src_intf, addr->ai_family);
+ if (! src_addr)
+ goto fail;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ timeout = opts->setupTimeout;
+#else
+ timeout = server->timeout;
+#endif
+
+ syslog(LOG_DEBUG, "Opening TCP connection to [%u] %s:%d "
+ "using timeout of %d second(s)",
+ i, strOrNil(dest_addr_str), get_addrinfo_port(addr),
+ timeout);
+
+ if (src_addr) {
+ src_addr_str = addrinfo_to_string(src_addr);
+ syslog(LOG_DEBUG, "Using source address: %s", strOrNil(src_addr_str));
+ free(src_addr_str);
+ src_addr_str = NULL;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ success = tac_connect_single_ev(sess, connControl->tac_event,
+ server->addrs, src_addr, timeout);
+#else
+ int fd = tac_connect_single(addr, server->secret, src_addr, timeout);
+ success = fd > 0 ? true : false;
+
+ server->fd = success ? fd : -1;
+#endif
+
+ if (server->src_intf)
+ free_interface_addrinfo(&src_addr);
+
+ if (!success) {
+fail:
+ syslog(LOG_DEBUG, "Failed to connect to %s",
+ strOrNil(dest_addr_str));
+ inc_failed_connects(i);
+
+ tacplus_server_activate_hold_down(tacplus_server(opts, i));
+
+ free(dest_addr_str);
+ dest_addr_str = NULL;
+ return false;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ tac_session_set_oob(sess, oob_callback);
+ tac_session_set_response(sess, response_callback);
+
+ /* In all releases so far we used "login' as authentication type. */
+ /* TODO: consider to make this configurable in future */
+ tac_session_set_authen_type(sess, TAC_PLUS_AUTHEN_TYPE_ASCII);
+
+ tac_session_set_secret(sess, server->secret);
+ tac_session_set_timeout(sess, server->timeout);
+
+ /* need to store index of opts->server[] into tac_session */
+ struct tac_session_extra *extra = tac_session_get_user_data(sess);
+ extra->server_id = i;
+ extra->server = server;
+ extra->state = UNINITIALIZED;
+
+ server->session = sess;
+#endif
+
+ syslog(LOG_DEBUG, "TCP connection to %s successfully opened",
+ strOrNil(dest_addr_str));
+ free(dest_addr_str);
+
+ return true;
+}
+
+static inline void
+tacplus_set_active_server(struct tacplus_options *opts,
+ const struct tacplus_options_server *server)
+{
+ opts->curr_server = server->id;
+}
+
+static inline void
+tacplus_clear_active_server(struct tacplus_options *opts)
+{
+ opts->curr_server = INVALID_SERVER_ID;
+}
+
+static inline bool
+tacplus_have_active_server(const struct tacplus_options *opts)
+{
+ return opts->curr_server < opts->n_servers ? true : false;
+}
+
+static inline void
+tacplus_set_next_server(struct tacplus_options *opts,
+ const struct tacplus_options_server *server)
+{
+ opts->next_server = server->id;
+}
+
+static inline void
+tacplus_clear_next_server(struct tacplus_options *opts)
+{
+ opts->next_server = INVALID_SERVER_ID;
+}
+
+static inline bool
+tacplus_have_next_server(const struct tacplus_options *opts)
+{
+ return opts->next_server < opts->n_servers ? true : false;
+}
+
+/*
+ * This is a somewhat limited use function, designed for initialising an
+ * iteration counter used for looping over the server list - see tacplus_connect()
+ */
+static unsigned
+tacplus_get_server_list_pos(struct tacplus_options *opts)
+{
+ /*
+ * If the next expiring, and higher priority, server's hold down timer has
+ * expired then start from the beginning of the server list. This allows us
+ * to choose the highest priority server which is available and updates
+ * next_server if necessary (during the connection loop routine).
+ */
+ if (tacplus_have_next_server(opts) &&
+ (! tacplus_server_is_held_down(tacplus_server(opts, opts->next_server)))) {
+ tacplus_clear_next_server(opts);
+ return HIGHEST_PRIO_SERVER_ID;
+ }
+ /*
+ * Otherwise if there is no active server then all servers are held down
+ * and there is no need to iterate the server list.
+ *
+ * In this condition there *should* always be a next_server set, if there
+ * isn't then we simply iterate from the highest priority server.
+ */
+ else if (! tacplus_have_active_server(opts)) {
+ if (! tacplus_have_next_server(opts)) {
+ syslog(LOG_DEBUG, "No active server and no next server");
+ return HIGHEST_PRIO_SERVER_ID;
+ }
+
+ return INVALID_SERVER_ID;
+ }
+
+ /*
+ * Otherwise if there isn't a higher priority server whose hold down timer
+ * has expired then start iterating the server list from the last used
+ * (active) server.
+ */
+ return opts->curr_server;
+}
+
+static void
+tacplus_update_next_server(struct tacplus_options *opts,
+ const struct tacplus_options_server *server)
+{
+ struct timespec cur_time, remaining, next_remaining;
+
+ if (! tacplus_have_next_server(opts)) {
+ tacplus_set_next_server(opts, server);
+ return;
+ }
+
+ cur_mono_time(&cur_time);
+ tacplus_server_remaining_hold_down_at(server, &cur_time, &remaining);
+ tacplus_server_remaining_hold_down_at(tacplus_server(opts, opts->next_server),
+ &cur_time, &next_remaining);
+
+ if (timespec_cmp(&remaining, &next_remaining) < 0)
+ tacplus_set_next_server(opts, server);
+}
+
+#ifdef HAVE_LIBTAC_EVENT
+unsigned tacplus_connect_all(void)
+{
+ struct tacplus_options *opts = connControl->opts;
+ unsigned tries, i, successes;
+ struct tac_session *sess;
+
+ syslog(LOG_DEBUG, "Number of servers in config: %d", opts->n_servers);
+
+ sess = tac_session_alloc_extra(sizeof(struct tac_session_extra));
+
+ for (successes = tries = 0, i = opts->curr_server; tries < opts->n_servers; tries++) {
+ i = (i + 1) % opts->n_servers;
+
+ if (opts->server[i].session)
+ continue;
+
+ if (! tacplus_server_is_held_down(tacplus_server(opts, i)) &&
+ tacplus_connect_ith(sess, i)) {
+ successes++;
+ sess = tac_session_alloc_extra(sizeof(struct tac_session_extra));
+ }
+ }
+
+ /* the last one will always be unused */
+ tac_session_free(sess);
+
+ return successes;
+}
+#endif
+
+static bool
+go_offline_until_next_hold_down_expiry(struct tacplus_options *opts)
+{
+ struct itimerspec it = {};
+ struct timespec *ts = &it.it_value;
+
+ if (! tacplus_have_next_server(opts)) {
+ syslog(LOG_DEBUG, "Cannot go offline (no next server)");
+ return false;
+ }
+
+ tacplus_server_remaining_hold_down(tacplus_server(opts, opts->next_server), ts);
+ syslog(LOG_DEBUG, "Setting offline timer for %lis %lins", ts->tv_sec, ts->tv_nsec);
+
+ return tacplusd_go_offline(ts);
+}
+
+bool tacplus_connect(void)
+{
+ static bool last_connect_failed;
+ struct tacplus_options *opts = connControl->opts;
+ bool all_servers_held_down = true;
+ unsigned i;
+
+ syslog(LOG_DEBUG, "Number of servers in config: %d", opts->n_servers);
+
+ /* If we are offline there is no point continuing */
+ if (! tacplusd_online()) {
+ syslog(LOG_INFO, "TACACS+ component offline");
+ goto fail;
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ struct tac_session *sess;
+
+ /* if the connection is already up and multiplexed, re-use it */
+ i = opts->curr_server;
+ if (opts->server[i].session) {
+ sess = opts->server[i].session;
+ if (sess->tac_multiplex)
+ return true;
+ }
+
+ sess = tac_session_alloc_extra(sizeof(struct tac_session_extra));
+#endif
+
+ for (i = tacplus_get_server_list_pos(opts); i < opts->n_servers; i++) {
+ if (! tacplus_server_is_held_down(tacplus_server(opts, i)) &&
+#ifdef HAVE_LIBTAC_EVENT
+ tacplus_connect_ith(sess, i)
+#else
+ tacplus_connect_ith(i)
+#endif
+ )
+ {
+ if (last_connect_failed) {
+ char *serv_addr = addrinfo_to_string(tacplus_server(opts, i)->addrs);
+ syslog(LOG_NOTICE, "Successfully connected to TACACS+ server at "
+ "%s following failure(s)", strOrNil(serv_addr));
+ free(serv_addr);
+ }
+
+ tacplus_set_active_server(opts, tacplus_server(opts, i));
+ last_connect_failed = false;
+ return true;
+ }
+
+ if (! tacplus_server_is_held_down(tacplus_server(opts, i))) {
+ if (all_servers_held_down)
+ tacplus_set_active_server(opts, tacplus_server(opts, i));
+
+ all_servers_held_down = false;
+ }
+
+ tacplus_update_next_server(opts, tacplus_server(opts, i));
+ }
+
+ if (all_servers_held_down) {
+ go_offline_until_next_hold_down_expiry(opts);
+ tacplus_clear_active_server(opts);
+ }
+
+#ifdef HAVE_LIBTAC_EVENT
+ tac_session_free(sess);
+#endif
+
+fail:
+ if (all_servers_held_down)
+ syslog(LOG_WARNING, "All servers have active hold down timers");
+
+ last_connect_failed = true;
+ return false;
+}
+
+#ifdef HAVE_LIBTAC_EVENT
+void tacplus_session_close(struct tac_session *sess)
+{
+ struct tac_session_extra *extra = (struct tac_session_extra *)tac_session_get_user_data(sess);
+
+ connControl->opts->server[extra->server_id].session = NULL;
+
+ /* not strictly necessary but useful for debugging... */
+ extra->server = NULL;
+
+ tac_session_free(sess);
+}
+#else
+void tacplus_close()
+{
+ struct tac_session_extra extra = {};
+
+ tacplus_current_session_extra (connControl->opts, &extra);
+ if (extra.server && extra.server->fd >= 0) {
+ close(extra.server->fd);
+ extra.server->fd = -1;
+ }
+}
+#endif
+
+static struct tacplus_options_server *
+tacplus_lookup_server_by_addr(struct tacplus_options *opts,
+ const struct sockaddr *saddr)
+{
+ struct tacplus_options_server *server;
+ unsigned i;
+
+ for (i = 0; i < opts->n_servers; i++) {
+ server = tacplus_server(opts, i);
+
+ if (sockaddr_addr_equal(server->addrs->ai_addr, saddr))
+ return server;
+ }
+
+ return NULL;
+}
+
+void
+tacplus_copy_server_state(struct tacplus_options *from_opts,
+ struct tacplus_options *to_opts)
+{
+ struct tacplus_options_server *new_server, *existing_server;
+ unsigned i;
+
+ assert(from_opts != to_opts);
+
+ for (i = 0; i < to_opts->n_servers; i++) {
+ new_server = tacplus_server(to_opts, i);
+ existing_server = tacplus_lookup_server_by_addr(
+ from_opts, new_server->addrs->ai_addr);
+ if (! existing_server)
+ continue;
+
+ new_server->state = existing_server->state;
+
+ /*
+ * If the existing server did not have a hold down timer configured
+ * then ensure all hold down state is cleared on the new server
+ */
+ if (existing_server->hold_down == 0)
+ tacplus_server_reset_hold_down(new_server);
+ }
+}
+
+struct tacplus_options *tacplus_options_alloc(unsigned n)
+{
+ struct tacplus_options *ret = NULL;
+
+ ret = calloc(1, sizeof(*ret) + (n * sizeof(ret->server[0])));
+ ret->n_servers = n;
+
+ for (unsigned i = 0; i < n; i++) {
+#ifndef HAVE_LIBTAC_EVENT
+ ret->server[i].fd = -1;
+#endif
+ tacplus_server_reset_hold_down(&ret->server[i]);
+ }
+
+ return ret;
+}
+
+struct tacplus_options *tacplus_parse_options(const char *file)
+{
+ struct tacplus_options *opts = NULL;
+
+ read_config(file, &opts);
+ return opts;
+}
+
+struct tacplus_options *tacplus_parse_reload_options(const char *file,
+ struct tacplus_options **cur_opts)
+{
+ struct tacplus_options *opts = tacplus_parse_options(file);
+
+ if (cur_opts && *cur_opts)
+ tacplus_reload_options(cur_opts, opts);
+
+ return opts;
+}
+
+struct tacplus_options *tacplus_reload_options(struct tacplus_options **cur_opts,
+ struct tacplus_options *new_opts)
+{
+ if (new_opts) {
+ tacplus_copy_server_state(*cur_opts, new_opts);
+
+ /* Now we have the server state populate the active and next server IDs */
+ tacplus_clear_active_server(new_opts);
+ TACPLUS_SERVER_LOOP(new_opts, serv) {
+ if (tacplus_server_is_held_down(serv))
+ tacplus_update_next_server(new_opts, serv);
+ else if (! tacplus_have_active_server(new_opts))
+ tacplus_set_active_server(new_opts, serv);
+ }
+
+ /*
+ * If we are currently offline then check to see if we can come back
+ * online. This is indicated by the presence of an "active" server.
+ * Otherwise we go (remain) offline based upon our current state
+ * and the new options.
+ */
+ if (! tacplusd_online()) {
+ if (tacplus_have_active_server(new_opts))
+ tacplusd_go_online();
+ else
+ go_offline_until_next_hold_down_expiry(new_opts);
+ }
+ }
+
+ cleanup_tacplus_options(cur_opts);
+ return new_opts;
+}
+
+void cleanup_tacplus_options(struct tacplus_options **opts)
+{
+ if (! opts || ! *opts)
+ return;
+
+ TACPLUS_SERVER_LOOP(*opts, serv) {
+ freeaddrinfo(serv->addrs);
+ freeaddrinfo(serv->src_addrs);
+ free((char *)serv->src_intf);
+ free((char *)serv->secret);
+ }
+
+ free(*opts);
+ *opts = NULL;
+}
diff --git a/tacplus-daemon/tacplus_srv_conn.h b/tacplus-daemon/tacplus_srv_conn.h
new file mode 100644
index 0000000..4b14ab8
--- /dev/null
+++ b/tacplus-daemon/tacplus_srv_conn.h
@@ -0,0 +1,149 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#ifndef TACPLUS_SRV_CONN_H
+#define TACPLUS_SRV_CONN_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <time.h>
+#include <libtac.h>
+
+#include "utils.h"
+
+#define TACPLUSD_IDLE_TIMEOUT 300
+#define TACPLUSD_REQUEST_RETRIES 3
+
+struct tacplus_options_server;
+
+struct tac_session_extra {
+ unsigned server_id;
+ struct tacplus_options_server *server;
+#ifdef HAVE_LIBTAC_EVENT
+ session_event_t state;
+#endif
+};
+
+struct tacplus_options
+{
+ unsigned n_servers, curr_server, next_server;
+ bool broadcast;
+ uint8_t dscp;
+ unsigned setupTimeout;
+ struct tacplus_options_server {
+ unsigned id;
+ struct addrinfo *addrs;
+ struct addrinfo *src_addrs;
+ const char *src_intf;
+ int timeout;
+ unsigned hold_down;
+ const char *secret;
+#ifdef HAVE_LIBTAC_EVENT
+ struct tac_session *session;
+#else
+ int fd;
+#endif
+ struct tacplus_server_state {
+ struct timespec lastTrouble;
+ } state;
+ } server[0];
+};
+
+#define HIGHEST_PRIO_SERVER_ID 0
+#define INVALID_SERVER_ID (TACPLUS_MAX_SERVERS + 1)
+
+#define TACPLUS_SERVER_LOOP(O,S) \
+ struct tacplus_options_server *S; \
+ for (unsigned _i = 0; _i < (O)->n_servers; _i++) \
+ if ((S = tacplus_server((O), _i)))
+
+struct tacplus_options *tacplus_options_alloc(unsigned n);
+
+static inline
+struct tacplus_options_server *tacplus_server(struct tacplus_options *opts, unsigned i)
+{
+ return i >= opts->n_servers ? NULL : (struct tacplus_options_server *)&opts->server[i];
+}
+
+#ifndef HAVE_LIBTAC_EVENT
+static inline
+struct tacplus_options_server *tacplus_current_server(struct tacplus_options *opts)
+{
+ return tacplus_server(opts, opts->curr_server);
+}
+
+/*
+ * Populate a tac_session_extra structure with the details of the currently
+ * active server.
+ *
+ * This reduces the amount of ifdef'd code required to support both event-driven
+ * and non-event-driven libtac implementations.
+ */
+static inline
+struct tac_session_extra *tacplus_current_session_extra(struct tacplus_options *opts,
+ struct tac_session_extra *extra)
+{
+ if (extra) {
+ extra->server_id = opts->curr_server;
+ extra->server = tacplus_current_server(opts);
+ }
+
+ return extra;
+}
+#else
+static inline
+struct tac_session *tacplus_session(struct tacplus_options *opts)
+{
+ if (opts->n_servers > 0)
+ return opts->server[opts->curr_server].session;
+ else
+ return NULL;
+}
+#endif
+
+bool tacplus_connect(void);
+unsigned tacplus_connect_all(void);
+
+struct tacplus_options *tacplus_parse_options(const char *);
+
+struct tacplus_options *tacplus_parse_reload_options(const char *file,
+ struct tacplus_options **cur_opts);
+
+struct tacplus_options *tacplus_reload_options(struct tacplus_options **cur_opts,
+ struct tacplus_options *new_opts);
+
+#ifdef HAVE_LIBTAC_EVENT
+void tacplus_session_close(struct tac_session *);
+#else
+void tacplus_close();
+#endif
+
+void cleanup_tacplus_options(struct tacplus_options **);
+
+bool
+tacplus_server_remaining_hold_down(const struct tacplus_options_server *server,
+ struct timespec *remaining);
+
+time_t
+tacplus_server_remaining_hold_down_secs(const struct tacplus_options_server *server);
+
+bool
+tacplus_server_is_held_down(const struct tacplus_options_server *server);
+
+void
+tacplus_server_activate_hold_down(struct tacplus_options_server *server);
+
+void
+tacplus_server_reset_hold_down(struct tacplus_options_server *server);
+
+void
+tacplus_copy_server_state(struct tacplus_options *from_opts,
+ struct tacplus_options *to_opts);
+
+#endif /* TACPLUS_SRV_CONN_H */
diff --git a/tacplus-daemon/tacplusd.suppress b/tacplus-daemon/tacplusd.suppress
new file mode 100644
index 0000000..e76a0d1
--- /dev/null
+++ b/tacplus-daemon/tacplusd.suppress
@@ -0,0 +1,31 @@
+{
+ pthread_create_signal_thread
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.2.5
+ fun:main
+}
+{
+ pthread_create_signal_service_setup_threads
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.2.5
+ fun:dbus_service_setup
+ fun:main
+}
+{
+ libdbus_noise_on_socketcall.sendmsg(msg.msg_iov[i])_unitalized
+ Memcheck:Param
+ socketcall.sendmsg(msg.msg_iov[i])
+ obj:/lib/x86_64-linux-gnu/libpthread-2.13.so
+ fun:_dbus_write_socket_with_unix_fds_two
+ fun:do_writing
+ fun:socket_do_iteration
+ fun:_dbus_transport_do_iteration
+ fun:_dbus_connection_do_iteration_unlocked
+ fun:_dbus_connection_read_write_dispatch
+ fun:dbus_service_listen
+ fun:start_thread
+}
diff --git a/tacplus-daemon/test/Makefile.am b/tacplus-daemon/test/Makefile.am
new file mode 100644
index 0000000..9ee991d
--- /dev/null
+++ b/tacplus-daemon/test/Makefile.am
@@ -0,0 +1,52 @@
+AM_CFLAGS = -Wall -Wextra -Werror $(HARDENING_CFLAGS) \
+ -I$(top_srcdir)/tacplus-daemon -DVERSION=\"$(VERSION)\" \
+ $(LIBTAC_CFLAGS) $(LIBTAC_EVENT_CFLAGS) $(SYSTEMD_CFLAGS) \
+ $(GLIB_CFLAGS) -DPROG="\"$(PACKAGE)\""
+
+AM_CXXFLAGS = -std=c++11 -Wall -Wextra -Werror -Wno-missing-field-initializers \
+ $(HARDENING_CXXFLAGS) \
+ $(cpputest_CFLAGS) -I$(top_srcdir)/tacplus-daemon \
+ $(LIBTAC_CFLAGS) $(LIBTAC_EVENT_CFLAGS) $(SYSTEMD_CFLAGS) \
+ $(GLIB_CFLAGS) -DVERSION=\"$(VERSION)\" -DPROG="\"$(PACKAGE)\""
+
+AM_CPPFLAGS = $(cpputest_CFLAGS)
+
+AM_LDFLAGS = $(HARDENING_CXXFLAGS)
+
+LDADD = $(cpputest_LIBS) $(LIBTAC_LIBS) $(LIBTAC_EVENT_LIBS) \
+ $(SYSTEMD_LIBS) $(GLIB_LIBS) -lpthread -lrt
+
+check_PROGRAMS = queue_tester server_connect_tester utils_tester parser_tester \
+ transaction_tester
+
+utils_tester_SOURCES = utilsTester.cpp ut_utils.c testMain.cpp ../utils.c
+utils_tester_LDADD = $(LDADD)
+
+parser_tester_SOURCES = parserTester.cpp testMain.cpp ../parser.c \
+ ../tacplus_srv_conn.c ../utils.c ../statistics.c \
+ ../dbus_service.c ../transaction.c ../queue.c \
+ ../global.c
+
+parser_tester_LDADD = $(LDADD)
+
+server_connect_tester_SOURCES = serverConnectTester.cpp testMain.cpp \
+ ut_utils.c ../tacplus_srv_conn.c ../parser.c \
+ ../utils.c ../statistics.c ../dbus_service.c \
+ ../transaction.c ../queue.c ../global.c
+server_connect_tester_LDADD = $(LDADD)
+server_connect_tester_LDFLAGS = -Wl,-wrap,tac_connect_single \
+ -Wl,-wrap,cur_mono_time \
+ -Wl,-wrap,tacplusd_go_online \
+ -Wl,-wrap,tacplusd_go_offline \
+ -Wl,-wrap,tacplusd_online
+
+queue_tester_SOURCES = queueTester.cpp testMain.cpp ../queue.c
+queue_tester_LDADD = $(LDADD)
+
+transaction_tester_SOURCES = transactionTester.cpp testMain.cpp ut_utils.c \
+ ../transaction.c ../tacplus_srv_conn.c \
+ ../parser.c ../utils.c ../statistics.c \
+ ../dbus_service.c ../queue.c ../global.c
+transaction_tester_LDADD = $(LDADD)
+
+TESTS = $(check_PROGRAMS)
diff --git a/tacplus-daemon/test/config/typical b/tacplus-daemon/test/config/typical
new file mode 100644
index 0000000..3f031ef
--- /dev/null
+++ b/tacplus-daemon/test/config/typical
@@ -0,0 +1,19 @@
+[general]
+BroadcastAccounting=false
+SetupTimeout=2
+Dscp=16
+[server1]
+Address=1.1.1.1
+Port=49
+Secret=foo
+Timeout=3
+HoldDown=0
+SourceInterface=eth0
+
+[server2]
+Address=2:2::2:2
+Port=200
+Secret=bar
+Timeout=10
+HoldDown=12
+SourceAddress=1:1::1:1
diff --git a/tacplus-daemon/test/config/typical-broadcast b/tacplus-daemon/test/config/typical-broadcast
new file mode 100644
index 0000000..a046be9
--- /dev/null
+++ b/tacplus-daemon/test/config/typical-broadcast
@@ -0,0 +1,18 @@
+[general]
+BroadcastAccounting=true
+SetupTimeout=4
+[server1]
+Address=1.1.1.2
+Port=149
+Secret=foobar
+Timeout=3
+HoldDown=0
+SourceAddress=2.2.2.2
+
+[server2]
+Address=2:2::2:2
+Port=85
+Secret=bar
+Timeout=15
+HoldDown=3600
+SourceInterface=eth3
diff --git a/tacplus-daemon/test/config/typical-changed-hold-down b/tacplus-daemon/test/config/typical-changed-hold-down
new file mode 100644
index 0000000..2eadebd
--- /dev/null
+++ b/tacplus-daemon/test/config/typical-changed-hold-down
@@ -0,0 +1,18 @@
+[general]
+BroadcastAccounting=false
+SetupTimeout=2
+[server1]
+Address=1.1.1.1
+Port=49
+Secret=foo
+Timeout=3
+HoldDown=10
+SourceInterface=eth0
+
+[server2]
+Address=2:2::2:2
+Port=200
+Secret=bar
+Timeout=10
+HoldDown=5
+SourceAddress=1:1::1:1
diff --git a/tacplus-daemon/test/parserTester.cpp b/tacplus-daemon/test/parserTester.cpp
new file mode 100644
index 0000000..b0a8362
--- /dev/null
+++ b/tacplus-daemon/test/parserTester.cpp
@@ -0,0 +1,221 @@
+/*
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "CppUTest/TestHarness.h"
+extern "C" {
+ #include <netinet/ip.h>
+ #include "parser.h"
+}
+
+TEST_GROUP(Parser)
+{
+ static void check_server_opts(struct tacplus_options::tacplus_options_server *expect_serv,
+ struct tacplus_options::tacplus_options_server *conf_serv)
+ {
+ CHECK(conf_serv != expect_serv);
+
+ UNSIGNED_LONGS_EQUAL(expect_serv->id, conf_serv->id);
+ CHECK(sockaddr_addr_equal(expect_serv->addrs->ai_addr,
+ conf_serv->addrs->ai_addr));
+ UNSIGNED_LONGS_EQUAL(get_addrinfo_port(expect_serv->addrs),
+ get_addrinfo_port(conf_serv->addrs));
+
+ if (! expect_serv->src_addrs) {
+ POINTERS_EQUAL(NULL, conf_serv->src_addrs);
+ }
+ else {
+ CHECK(sockaddr_addr_equal(expect_serv->src_addrs->ai_addr,
+ conf_serv->src_addrs->ai_addr));
+ }
+
+ if (! expect_serv->src_intf) {
+ POINTERS_EQUAL(NULL, conf_serv->src_intf);
+ }
+ else {
+ STRCMP_EQUAL(expect_serv->src_intf, conf_serv->src_intf);
+ }
+
+ STRCMP_EQUAL(expect_serv->secret, conf_serv->secret);
+ UNSIGNED_LONGS_EQUAL(expect_serv->hold_down, conf_serv->hold_down);
+ UNSIGNED_LONGS_EQUAL(expect_serv->timeout, conf_serv->timeout);
+
+#ifdef HAVE_LIBTAC_EVENT
+ CHECK(! conf_serv->session);
+#else
+ LONGS_EQUAL(-1, conf_serv->fd);
+#endif
+ }
+
+ void test_typical(struct tacplus_options *loaded_opts)
+ {
+ struct tacplus_options::tacplus_options_server *serv1, *serv2;
+
+ struct tacplus_options::tacplus_options_server expect_serv1 = {
+ .id = 0,
+ .addrs = tacplus_addrinfo("1.1.1.1", "49"),
+ .src_addrs = NULL,
+ .src_intf = "eth0",
+ .timeout = 3,
+ .hold_down = 0,
+ .secret = "foo",
+ };
+
+ struct tacplus_options::tacplus_options_server expect_serv2 = {
+ .id = 1,
+ .addrs = tacplus_addrinfo("2:2::2:2", "200"),
+ .src_addrs = tacplus_addrinfo("1:1::1:1", ""),
+ .src_intf = NULL,
+ .timeout = 10,
+ .hold_down = 12,
+ .secret = "bar",
+ };
+
+ CHECK(loaded_opts);
+
+ CHECK(!loaded_opts->broadcast);
+ UNSIGNED_LONGS_EQUAL(2, loaded_opts->setupTimeout);
+ UNSIGNED_LONGS_EQUAL(16<<2, loaded_opts->dscp);
+
+ UNSIGNED_LONGS_EQUAL(2, loaded_opts->n_servers);
+ UNSIGNED_LONGS_EQUAL(0, loaded_opts->curr_server);
+ UNSIGNED_LONGS_EQUAL(INVALID_SERVER_ID, loaded_opts->next_server);
+
+ serv1 = (struct tacplus_options::tacplus_options_server *) tacplus_server(loaded_opts, 0);
+ check_server_opts(&expect_serv1, serv1);
+
+ serv2 = (struct tacplus_options::tacplus_options_server *) tacplus_server(loaded_opts, 1);
+ check_server_opts(&expect_serv2, serv2);
+ }
+
+ void test_typical_changed_hold_down(struct tacplus_options *loaded_opts)
+ {
+ struct tacplus_options::tacplus_options_server *serv1, *serv2;
+
+ struct tacplus_options::tacplus_options_server expect_serv1 = {
+ .id = 0,
+ .addrs = tacplus_addrinfo("1.1.1.1", "49"),
+ .src_addrs = NULL,
+ .src_intf = "eth0",
+ .timeout = 3,
+ .hold_down = 10,
+ .secret = "foo",
+ };
+
+ struct tacplus_options::tacplus_options_server expect_serv2 = {
+ .id = 1,
+ .addrs = tacplus_addrinfo("2:2::2:2", "200"),
+ .src_addrs = tacplus_addrinfo("1:1::1:1", ""),
+ .src_intf = NULL,
+ .timeout = 10,
+ .hold_down = 5,
+ .secret = "bar",
+ };
+
+ CHECK(loaded_opts);
+
+ CHECK(!loaded_opts->broadcast);
+ UNSIGNED_LONGS_EQUAL(2, loaded_opts->setupTimeout);
+ UNSIGNED_LONGS_EQUAL(IPTOS_CLASS_CS6, loaded_opts->dscp);
+
+ UNSIGNED_LONGS_EQUAL(2, loaded_opts->n_servers);
+ UNSIGNED_LONGS_EQUAL(0, loaded_opts->curr_server);
+ UNSIGNED_LONGS_EQUAL(INVALID_SERVER_ID, loaded_opts->next_server);
+
+ serv1 = (struct tacplus_options::tacplus_options_server *) tacplus_server(loaded_opts, 0);
+ check_server_opts(&expect_serv1, serv1);
+
+ serv2 = (struct tacplus_options::tacplus_options_server *) tacplus_server(loaded_opts, 1);
+ check_server_opts(&expect_serv2, serv2);
+ }
+};
+
+TEST(Parser, nonExistentFile)
+{
+ struct tacplus_options *opts = tacplus_parse_options("config/UNKNOWN");
+ POINTERS_EQUAL(NULL, opts);
+}
+
+TEST(Parser, typical)
+{
+ struct tacplus_options *opts = tacplus_parse_options("config/typical");
+ test_typical(opts);
+
+ cleanup_tacplus_options(&opts);
+ POINTERS_EQUAL(NULL, opts);
+}
+
+TEST(Parser, typicalChangedHoldDown)
+{
+ struct tacplus_options *opts = tacplus_parse_options("config/typical-changed-hold-down");
+ test_typical_changed_hold_down(opts);
+
+ cleanup_tacplus_options(&opts);
+ POINTERS_EQUAL(NULL, opts);
+}
+
+TEST(Parser, reloadTypicalChangedHoldDown)
+{
+ struct tacplus_options *opts = tacplus_parse_options("config/typical");
+ test_typical(opts);
+
+ struct tacplus_options *new_opts = tacplus_parse_reload_options(
+ "config/typical-changed-hold-down", &opts);
+ POINTERS_EQUAL(NULL, opts);
+ test_typical_changed_hold_down(new_opts);
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(Parser, typicalBroadcast)
+{
+ struct tacplus_options::tacplus_options_server *serv1, *serv2;
+
+ struct tacplus_options::tacplus_options_server expect_serv1 = {
+ .id = 0,
+ .addrs = tacplus_addrinfo("1.1.1.2", "149"),
+ .src_addrs = tacplus_addrinfo("2.2.2.2", ""),
+ .src_intf = NULL,
+ .timeout = 3,
+ .hold_down = 0,
+ .secret = "foobar",
+ };
+
+ struct tacplus_options::tacplus_options_server expect_serv2 = {
+ .id = 1,
+ .addrs = tacplus_addrinfo("2:2::2:2", "85"),
+ .src_addrs = NULL,
+ .src_intf = "eth3",
+ .timeout = 15,
+ .hold_down = 3600,
+ .secret = "bar",
+ };
+
+ struct tacplus_options *opts = tacplus_parse_options("config/typical-broadcast");
+ CHECK(opts);
+
+#ifdef HAVE_LIBTAC_EVENT
+ CHECK(opts->broadcast);
+#else
+ CHECK(!opts->broadcast);
+#endif
+
+ UNSIGNED_LONGS_EQUAL(4, opts->setupTimeout);
+ UNSIGNED_LONGS_EQUAL(IPTOS_CLASS_CS6, opts->dscp);
+
+ UNSIGNED_LONGS_EQUAL(2, opts->n_servers);
+ UNSIGNED_LONGS_EQUAL(0, opts->curr_server);
+ UNSIGNED_LONGS_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ serv1 = (struct tacplus_options::tacplus_options_server *) tacplus_server(opts, 0);
+ check_server_opts(&expect_serv1, serv1);
+
+ serv2 = (struct tacplus_options::tacplus_options_server *) tacplus_server(opts, 1);
+ check_server_opts(&expect_serv2, serv2);
+
+ cleanup_tacplus_options(&opts);
+ POINTERS_EQUAL(NULL, opts);
+}
diff --git a/tacplus-daemon/test/queueTester.cpp b/tacplus-daemon/test/queueTester.cpp
new file mode 100644
index 0000000..a24818d
--- /dev/null
+++ b/tacplus-daemon/test/queueTester.cpp
@@ -0,0 +1,143 @@
+/*
+ Copyright (c) 2018 AT&T Intellectual Property.
+ Copyright (c) 2015 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "CppUTest/TestHarness.h"
+extern "C" {
+ #include "queue.h"
+}
+
+struct test_elem {
+ int num;
+ void *opaque;
+};
+
+TEST_GROUP(Queueing)
+{
+ Queue *q;
+
+ void setup() {
+ q = create_queue(NULL);
+ CHECK(q);
+ }
+
+ void teardown (void) {
+ destroy_queue(&q);
+ POINTERS_EQUAL(NULL, q);
+ }
+};
+
+TEST(Queueing, EnqueueSingle)
+{
+ struct test_elem *pair = (struct test_elem *)malloc(sizeof(struct test_elem));
+ int empty;
+
+ POINTERS_EQUAL(NULL, q->front);
+ POINTERS_EQUAL(NULL, q->rear);
+
+ enqueue(q, pair);
+
+ CHECK(q->front != NULL);
+ CHECK(q->rear != NULL);
+ CHECK(q->front == q->rear);
+
+ empty = is_queue_empty(q);
+ LONGS_EQUAL(0, empty)
+
+ /* memory pointed to by pair will be freed in teardown */
+};
+
+TEST(Queueing, DequeueEmpty)
+{
+ struct test_elem *pair_consumed;
+ int empty;
+
+ POINTERS_EQUAL(NULL, q->front);
+ POINTERS_EQUAL(NULL, q->rear);
+
+ empty = is_queue_empty(q);
+ LONGS_EQUAL(1, empty);
+
+ pair_consumed = (struct test_elem *)dequeue(q);
+ POINTERS_EQUAL(NULL, pair_consumed);
+ LONGS_EQUAL(1, empty);
+};
+
+TEST(Queueing, DequeueNonEmpty)
+{
+ struct test_elem *pair = (struct test_elem *)malloc(sizeof(struct test_elem));
+ struct test_elem *pair_consumed;
+ int empty;
+
+ POINTERS_EQUAL(NULL, q->front);
+ POINTERS_EQUAL(NULL, q->rear);
+
+ enqueue(q, pair);
+
+ CHECK(q->front != NULL);
+ CHECK(q->rear != NULL);
+
+ pair_consumed = (struct test_elem *)dequeue(q);
+ POINTERS_EQUAL(pair, pair_consumed);
+
+ empty = is_queue_empty(q);
+ LONGS_EQUAL(1, empty)
+
+ free(pair);
+};
+
+TEST(Queueing, DequeueMultiple)
+{
+ struct test_elem *pair1 = (struct test_elem *)malloc(sizeof(struct test_elem));
+ struct test_elem *pair2 = (struct test_elem *)malloc(sizeof(struct test_elem));
+ struct test_elem *pair3 = (struct test_elem *)malloc(sizeof(struct test_elem));
+ struct test_elem *pair_consumed1;
+ struct test_elem *pair_consumed2;
+ struct test_elem *pair_consumed3;
+ Node *rear;
+ int empty;
+
+ POINTERS_EQUAL(NULL, q->front);
+ POINTERS_EQUAL(NULL, q->rear);
+
+ rear = q->rear;
+ enqueue(q, pair1);
+ CHECK(q->rear != rear);
+
+ CHECK(q->front != NULL);
+ CHECK(q->rear != NULL);
+ POINTERS_EQUAL(q->front, q->rear);
+
+ rear = q->rear;
+ enqueue(q, pair2);
+ CHECK(q->rear != rear);
+
+ CHECK(q->front != q->rear);
+
+ rear = q->rear;
+ enqueue(q, pair3);
+ CHECK(q->rear != rear);
+
+ CHECK(q->front != q->rear);
+
+ LONGS_EQUAL(0, empty);
+
+ pair_consumed1 = (struct test_elem *)dequeue(q);
+ POINTERS_EQUAL(pair1, pair_consumed1);
+ free(pair1);
+
+ pair_consumed2 = (struct test_elem *)dequeue(q);
+ POINTERS_EQUAL(pair2, pair_consumed2);
+ free(pair2);
+
+ pair_consumed3 = (struct test_elem *)dequeue(q);
+ POINTERS_EQUAL(pair3, pair_consumed3);
+ free(pair3);
+
+ empty = is_queue_empty(q);
+ LONGS_EQUAL(1, empty);
+};
+
diff --git a/tacplus-daemon/test/serverConnectTester.cpp b/tacplus-daemon/test/serverConnectTester.cpp
new file mode 100644
index 0000000..98654ba
--- /dev/null
+++ b/tacplus-daemon/test/serverConnectTester.cpp
@@ -0,0 +1,1527 @@
+/*
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "CppUTest/TestHarness.h"
+extern "C" {
+ #include "tacplus_srv_conn.h"
+ #include "global.h"
+ #include "parser.h"
+ #include "statistics.h"
+}
+#include "ut_utils.h"
+
+TEST_GROUP(ServerConnection) {
+
+#define SET_OPTS_SERVER(O,I,A,P) \
+ { \
+ CHECK(I < O->n_servers); \
+ O->server[I].id = I; \
+ O->server[I].addrs = tacplus_addrinfo(A, P); \
+ SET_OPTS_SERVER_SRC_ADDR(O, I, NULL); \
+ O->server[I].timeout = (I+1)*5; \
+ O->server[I].secret = strdup(#I); \
+ }
+
+#define SET_OPTS_SERVER_SRC_ADDR(O,I,S) \
+ { \
+ O->server[I].src_addrs = S ? tacplus_addrinfo(S, "0") : NULL; \
+ }
+
+ static const int num_servers = 3;
+
+ struct tacplus_options *opts;
+
+ void setup()
+ {
+ CHECK_EQUAL(0, create_statistics(num_servers));
+
+ connControl->opts = opts = tacplus_options_alloc(num_servers);
+ CHECK(opts);
+
+ SET_OPTS_SERVER(opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(opts, 1, "2.2.2.2", "2");
+ SET_OPTS_SERVER(opts, 2, "3:3::3:3", "3");
+ }
+
+ void teardown()
+ {
+ cleanup_tacplus_options(&opts);
+ POINTERS_EQUAL(NULL, opts);
+
+ free_statistics();
+
+ ut_reset_tac_connect_wrapper();
+ }
+
+};
+
+#define PORT_STR_LEN (strlen("65535") + 1)
+
+static struct tacplus_options *copy_tacplus_options(struct tacplus_options *src)
+{
+ struct tacplus_options *new_opts = tacplus_options_alloc(src->n_servers);
+ CHECK(new_opts);
+
+ memcpy(new_opts, src, sizeof(*src));
+
+ for (unsigned i = 0; i < src->n_servers; i++) {
+ struct tacplus_options::tacplus_options_server *server = &(src->server[i]);
+
+ /* Quick and dirty copy for simple params */
+ memcpy(&new_opts->server[server->id], server, sizeof(*server));
+
+ /* Now need to properly copy params which point to allocated memory */
+ char port_str[PORT_STR_LEN];
+ int ret = snprintf(port_str, sizeof(port_str),
+ "%u", get_addrinfo_port(server->addrs));
+ CHECK(ret > 0 && ret < (int) sizeof(port_str));
+
+ SET_OPTS_SERVER(new_opts, server->id,
+ addrinfo_to_string(server->addrs), port_str);
+ if (server->src_addrs)
+ SET_OPTS_SERVER_SRC_ADDR(new_opts, server->id,
+ addrinfo_to_string(server->src_addrs));
+ }
+
+ return new_opts;
+}
+
+TEST(ServerConnection, initOptions)
+{
+ CHECK_EQUAL(num_servers, opts->n_servers);
+ CHECK_EQUAL(HIGHEST_PRIO_SERVER_ID, opts->curr_server);
+
+ for (unsigned i = 0; i < opts->n_servers; i++) {
+ struct tacplus_options::tacplus_options_server *serv = \
+ (struct tacplus_options::tacplus_options_server *) tacplus_server(opts, i);
+#ifndef HAVE_LIBTAC_EVENT
+ CHECK_EQUAL(-1, serv->fd);
+#endif
+ CHECK_TIMESPEC_VALS(serv->state.lastTrouble, -1, -1);
+ }
+}
+
+TEST(ServerConnection, lookupServer)
+{
+ struct tacplus_options::tacplus_options_server *serv;
+
+ for (int i = 0; i < num_servers; i++) {
+ serv = (struct tacplus_options::tacplus_options_server *) tacplus_server(opts, i);
+ CHECK_EQUAL(i, serv->id);
+ }
+
+ /* Check out of bounds request */
+ POINTERS_EQUAL(NULL, tacplus_server(opts, num_servers));
+ POINTERS_EQUAL(NULL, tacplus_server(opts, num_servers+9999));
+ POINTERS_EQUAL(NULL, tacplus_server(opts, -1));
+}
+
+#ifndef HAVE_LIBTAC_EVENT
+TEST(ServerConnection, lookupCurrentServer)
+{
+ struct tacplus_options::tacplus_options_server *serv;
+
+ CHECK_EQUAL(0, opts->curr_server);
+ serv = (struct tacplus_options::tacplus_options_server *) tacplus_current_server(opts);
+ CHECK_EQUAL(0, serv->id);
+
+ opts->curr_server = 2;
+ serv = (struct tacplus_options::tacplus_options_server *) tacplus_current_server(opts);
+ CHECK_EQUAL(2, serv->id);
+
+ opts->curr_server = INVALID_SERVER_ID;
+ POINTERS_EQUAL(NULL, tacplus_current_server(opts));
+}
+
+TEST(ServerConnection, lookupCurrentSessionExtra)
+{
+ struct tac_session_extra extra, *ret;
+
+ CHECK_EQUAL(0, opts->curr_server);
+ ret = tacplus_current_session_extra(opts, &extra);
+
+ POINTERS_EQUAL(&extra, ret);
+ CHECK_EQUAL(0, extra.server_id);
+ POINTERS_EQUAL(&opts->server[0], extra.server);
+
+ opts->curr_server = 2;
+ ret = tacplus_current_session_extra(opts, &extra);
+
+ POINTERS_EQUAL(&extra, ret);
+ CHECK_EQUAL(2, extra.server_id);
+ POINTERS_EQUAL(&opts->server[2], extra.server);
+
+ opts->curr_server = INVALID_SERVER_ID;
+ ret = tacplus_current_session_extra(opts, &extra);
+
+ POINTERS_EQUAL(&extra, ret);
+ CHECK_EQUAL(INVALID_SERVER_ID, extra.server_id);
+ POINTERS_EQUAL(NULL, extra.server);
+}
+
+TEST(ServerConnection, connectSingleServerNoHoldDown)
+{
+ opts->n_servers = 1;
+ SET_OPTS_SERVER(opts, 0, "127.0.0.1", "100");
+ SET_OPTS_SERVER_SRC_ADDR(opts, 0, "10.10.10.10");
+
+ int fds[] = { 1, -1, 2 };
+ ut_set_tac_connect_fds(fds, ARRAY_SIZE(fds));
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(1, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ CHECK_FALSE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(2, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ struct tac_connect_call exp_calls[] = {
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ };
+
+ struct tac_connect_call *calls;
+ int calls_len = ut_get_tac_connect_calls(&calls);
+ LONGS_EQUAL(ARRAY_SIZE(exp_calls), calls_len);
+
+ for (int i = 0; i < calls_len; i++)
+ CHECK_TRUE(ut_tac_connect_call_eq(&exp_calls[i], &calls[i]));
+}
+
+TEST(ServerConnection, connectSingleServerHoldDown)
+{
+ opts->n_servers = 1;
+ SET_OPTS_SERVER(opts, 0, "127.0.0.1", "100");
+ opts->server[0].hold_down = 10;
+
+ int fds[] = { 1, -1, 2 };
+ ut_set_tac_connect_fds(fds, ARRAY_SIZE(fds));
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(1, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ /* Check connect calls without a secret */
+ free((void *) opts->server[0].secret);
+ opts->server[0].secret = NULL;
+
+ CHECK_FALSE(tacplus_connect());
+ CHECK_FALSE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* Server is held down so there should be no attempt to connect */
+ CHECK_FALSE(tacplus_connect());
+ CHECK_FALSE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* Expire hold down timer */
+ ut_inc_cur_mono_time(20, 0);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(2, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ struct tac_connect_call exp_calls[] = {
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = NULL,
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = NULL,
+ .timeout = 5,
+ },
+ };
+
+ struct tac_connect_call *calls;
+ int calls_len = ut_get_tac_connect_calls(&calls);
+ LONGS_EQUAL(ARRAY_SIZE(exp_calls), calls_len);
+
+ for (int i = 0; i < calls_len; i++)
+ CHECK_TRUE(ut_tac_connect_call_eq(&exp_calls[i], &calls[i]));
+}
+
+TEST(ServerConnection, connectMultiServerNoHoldDown)
+{
+ opts->n_servers = 2;
+ SET_OPTS_SERVER(opts, 0, "127.0.0.1", "100");
+ SET_OPTS_SERVER(opts, 1, "127.0.0.2", "100");
+ SET_OPTS_SERVER_SRC_ADDR(opts, 0, "10.10.20.20");
+ SET_OPTS_SERVER_SRC_ADDR(opts, 1, "20.20.10.10");
+
+ int fds[] = { 1, -1, 2, -1, -1, 3 };
+ ut_set_tac_connect_fds(fds, ARRAY_SIZE(fds));
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(1, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(2, opts->server[1].fd)
+ CHECK_EQUAL(1, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* Both servers fail to connect */
+ CHECK_FALSE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(-1, opts->server[1].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* Back to server 0 */
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(3, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ struct tac_connect_call exp_calls[] = {
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = *(opts->server[1].src_addrs->ai_addr),
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = *(opts->server[1].src_addrs->ai_addr),
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ };
+
+ struct tac_connect_call *calls;
+ int calls_len = ut_get_tac_connect_calls(&calls);
+ LONGS_EQUAL(ARRAY_SIZE(exp_calls), calls_len);
+
+ for (int i = 0; i < calls_len; i++)
+ CHECK_TRUE(ut_tac_connect_call_eq(&exp_calls[i], &calls[i]));
+}
+
+TEST(ServerConnection, connectMultiServerHoldDown)
+{
+ SET_OPTS_SERVER(opts, 0, "127.0.0.1", "100");
+ SET_OPTS_SERVER(opts, 1, "127.0.0.2", "100");
+ SET_OPTS_SERVER(opts, 2, "127.0.0.3", "100");
+ opts->server[0].hold_down = 10;
+ opts->server[1].hold_down = 10;
+ opts->server[2].hold_down = 10;
+ SET_OPTS_SERVER_SRC_ADDR(opts, 0, "10.10.10.10");
+ SET_OPTS_SERVER_SRC_ADDR(opts, 2, "11.11.11.11");
+
+ int fds[] = { 1, -1, 2, -1, 3, -1, 4, 5 };
+ ut_set_tac_connect_fds(fds, ARRAY_SIZE(fds));
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(1, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(2, opts->server[1].fd)
+ CHECK_EQUAL(1, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(-1, opts->server[1].fd);
+ CHECK_EQUAL(3, opts->server[2].fd);
+ CHECK_EQUAL(2, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ CHECK_FALSE(tacplus_connect());
+ CHECK_FALSE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(-1, opts->server[1].fd);
+ CHECK_EQUAL(-1, opts->server[2].fd);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* All servers are held down so there should be no attempt to connect to any */
+ CHECK_FALSE(tacplus_connect());
+ CHECK_FALSE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(-1, opts->server[1].fd);
+ CHECK_EQUAL(-1, opts->server[2].fd);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* Expire hold down timers */
+ ut_inc_cur_mono_time(20, 0);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(4, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ /* Should stick with server 0 on a subsequent connect */
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(5, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ struct tac_connect_call exp_calls[] = {
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[2].addrs->ai_addr),
+ .source_addr = *(opts->server[2].src_addrs->ai_addr),
+ .key = "2",
+ .timeout = 15,
+ },
+ {
+ .server_addr = *(opts->server[2].addrs->ai_addr),
+ .source_addr = *(opts->server[2].src_addrs->ai_addr),
+ .key = "2",
+ .timeout = 15,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = *(opts->server[0].src_addrs->ai_addr),
+ .key = "0",
+ .timeout = 5,
+ },
+ };
+
+ struct tac_connect_call *calls;
+ int calls_len = ut_get_tac_connect_calls(&calls);
+ LONGS_EQUAL(ARRAY_SIZE(exp_calls), calls_len);
+
+ for (int i = 0; i < calls_len; i++)
+ CHECK_TRUE(ut_tac_connect_call_eq(&exp_calls[i], &calls[i]));
+}
+
+TEST(ServerConnection, connectMultiServerDiffHoldDown)
+{
+ SET_OPTS_SERVER(opts, 0, "127.0.0.1", "100");
+ SET_OPTS_SERVER(opts, 1, "127.0.0.2", "100");
+ SET_OPTS_SERVER(opts, 2, "127.0.0.3", "100");
+ opts->server[0].hold_down = 10;
+ opts->server[1].hold_down = 5;
+ opts->server[2].hold_down = 0;
+
+ int fds[] = { 1, -1, 2, -1, 3, -1, 4, 5, 6, 7 };
+ ut_set_tac_connect_fds(fds, ARRAY_SIZE(fds));
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(1, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(2, opts->server[1].fd)
+ CHECK_EQUAL(1, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(-1, opts->server[1].fd);
+ CHECK_EQUAL(3, opts->server[2].fd);
+ CHECK_EQUAL(2, opts->curr_server);
+ CHECK_EQUAL(1, opts->next_server);
+
+ CHECK_FALSE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(-1, opts->server[0].fd);
+ CHECK_EQUAL(-1, opts->server[1].fd);
+ CHECK_EQUAL(-1, opts->server[2].fd);
+ CHECK_EQUAL(2, opts->curr_server);
+ CHECK_EQUAL(2, opts->next_server);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(4, opts->server[2].fd);
+ CHECK_EQUAL(2, opts->curr_server);
+ CHECK_EQUAL(1, opts->next_server);
+
+ /* Expire hold down timer for server 1 */
+ ut_inc_cur_mono_time(6, 0);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(5, opts->server[1].fd);
+ CHECK_EQUAL(1, opts->curr_server);
+ CHECK_EQUAL(0, opts->next_server);
+
+ /* Expire hold down timer for server 0 */
+ ut_inc_cur_mono_time(20, 0);
+
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(6, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ /* Should stick with server 0 on a subsequent connect */
+ CHECK_TRUE(tacplus_connect());
+ CHECK_TRUE(tacplusd_online());
+ CHECK_EQUAL(7, opts->server[0].fd);
+ CHECK_EQUAL(0, opts->curr_server);
+ CHECK_EQUAL(INVALID_SERVER_ID, opts->next_server);
+
+ struct tac_connect_call exp_calls[] = {
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[2].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "2",
+ .timeout = 15,
+ },
+ {
+ .server_addr = *(opts->server[2].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "2",
+ .timeout = 15,
+ },
+ {
+ .server_addr = *(opts->server[2].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "2",
+ .timeout = 15,
+ },
+ {
+ .server_addr = *(opts->server[1].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "1",
+ .timeout = 10,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "0",
+ .timeout = 5,
+ },
+ {
+ .server_addr = *(opts->server[0].addrs->ai_addr),
+ .source_addr = {0},
+ .key = "0",
+ .timeout = 5,
+ },
+ };
+
+ struct tac_connect_call *calls;
+ int calls_len = ut_get_tac_connect_calls(&calls);
+ LONGS_EQUAL(ARRAY_SIZE(exp_calls), calls_len);
+
+ for (int i = 0; i < calls_len; i++)
+ CHECK_TRUE(ut_tac_connect_call_eq(&exp_calls[i], &calls[i]));
+}
+#endif
+
+/*
+ * Hold down timer tests
+ */
+
+TEST(ServerConnection, activateHoldDown)
+{
+ struct tacplus_options::tacplus_options_server *serv = &opts->server[0];
+
+ CHECK_TIMESPEC_VALS(serv->state.lastTrouble, -1, -1);
+ serv->hold_down = 10;
+
+ ut_set_cur_mono_time(1245, 19828);
+ tacplus_server_activate_hold_down((struct tacplus_options_server *)serv);
+ CHECK_TIMESPEC_VALS(serv->state.lastTrouble, 1245, 19828);
+ CHECK(tacplus_server_is_held_down((const struct tacplus_options_server *)serv));
+
+ ut_set_cur_mono_time(97876757, 87687);
+ tacplus_server_activate_hold_down((struct tacplus_options_server *)serv);
+ CHECK_TIMESPEC_VALS(serv->state.lastTrouble, 97876757, 87687);
+ CHECK(tacplus_server_is_held_down((const struct tacplus_options_server *)serv));
+}
+
+TEST(ServerConnection, resetHoldDown)
+{
+ struct tacplus_options::tacplus_options_server *serv = &opts->server[0];
+
+ ut_set_cur_mono_time(110, 0);
+ serv->hold_down = 10;
+
+ SET_TIMESPEC_VALS(serv->state.lastTrouble, 105, 20221);
+ CHECK_TIMESPEC_VALS(serv->state.lastTrouble, 105, 20221);
+
+ CHECK(tacplus_server_is_held_down((const struct tacplus_options_server *)serv));
+
+ tacplus_server_reset_hold_down((struct tacplus_options_server *)serv);
+ CHECK_TIMESPEC_VALS(serv->state.lastTrouble, -1, -1);
+
+ CHECK(! tacplus_server_is_held_down((const struct tacplus_options_server *)serv));
+}
+
+TEST(ServerConnection, remainingHoldDown)
+{
+ struct tacplus_options::tacplus_options_server serv = {};
+ struct timespec remaining;
+ bool held_down;
+
+ ut_set_cur_mono_time(20, 500);
+
+ /* No hold down configured, and no connection issues detected */
+ UNSIGNED_LONGS_EQUAL(0, serv.hold_down);
+ CHECK_TIMESPEC_VALS(serv.state.lastTrouble, 0, 0);
+
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK_FALSE(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 0);
+
+ /* Configure a hold down, still no connection issues detected */
+ serv.hold_down = 10;
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK_FALSE(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 0);
+
+ /* Experience some connection trouble */
+ SET_TIMESPEC_VALS(serv.state.lastTrouble, 18, 99769);
+
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 8, 99269);
+
+ /* Some time passes */
+ ut_set_cur_mono_time(24, 921978);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 3, 999177791);
+
+ /* Not quite enough time passes */
+ ut_set_cur_mono_time(28, 99768);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 1);
+
+ /* Exactly enough time passes */
+ ut_set_cur_mono_time(28, 99769);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK_FALSE(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 0);
+
+ /* Some more time passes */
+ ut_set_cur_mono_time(30, 10029);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK_FALSE(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 0);
+
+ /* Experience some more connection trouble */
+ SET_TIMESPEC_VALS(serv.state.lastTrouble, 30, 10029);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 10, 0);
+
+ /* Some time passes */
+ ut_set_cur_mono_time(32, 100);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 8, 9929);
+
+ /* Enough time passes to expire timer again */
+ ut_set_cur_mono_time(50, 89817);
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK_FALSE(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 0);
+}
+
+TEST(ServerConnection, remainingHoldDownConfChange)
+{
+ struct tacplus_options::tacplus_options_server serv = {};
+ struct timespec remaining;
+ bool held_down;
+
+ /* Hold down timer is active */
+ ut_set_cur_mono_time(20, 500);
+ serv.hold_down = 15;
+ SET_TIMESPEC_VALS(serv.state.lastTrouble, 14, 21984);
+
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 9, 21484);
+
+ /* Hold down timer increases */
+ serv.hold_down += 30;
+ ut_set_cur_mono_time(24, 98897);
+
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 34, 999923087);
+
+ /* Hold down timer decreases */
+ serv.hold_down -= 21;
+ ut_set_cur_mono_time(26, 71298);
+
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 11, 999950686);
+
+ /* Hold down timer disabled */
+ serv.hold_down = 0;
+
+ held_down = tacplus_server_remaining_hold_down((const struct tacplus_options_server *)
+ &serv, &remaining);
+ CHECK_FALSE(held_down);
+ CHECK_TIMESPEC_VALS(remaining, 0, 0);
+}
+
+TEST(ServerConnection, isHeldDown)
+{
+ struct tacplus_options::tacplus_options_server serv = {};
+ bool held_down;
+
+ held_down = tacplus_server_is_held_down((const struct tacplus_options_server *) &serv);
+ CHECK_FALSE(held_down);
+
+ /* Hold down timer is active */
+ ut_set_cur_mono_time(30, 2480);
+ serv.hold_down = 25;
+ SET_TIMESPEC_VALS(serv.state.lastTrouble, 24, 21984);
+
+ held_down = tacplus_server_is_held_down((const struct tacplus_options_server *) &serv);
+ CHECK(held_down);
+
+ /* Hold down timer increases */
+ serv.hold_down += 30;
+ ut_set_cur_mono_time(34, 98897);
+
+ held_down = tacplus_server_is_held_down((const struct tacplus_options_server *) &serv);
+ CHECK(held_down);
+
+ /* Hold down timer decreases */
+ serv.hold_down -= 21;
+ ut_set_cur_mono_time(36, 71298);
+
+ held_down = tacplus_server_is_held_down((const struct tacplus_options_server *) &serv);
+ CHECK(held_down);
+
+ /* Hold down timer disabled */
+ serv.hold_down = 0;
+
+ held_down = tacplus_server_is_held_down((const struct tacplus_options_server *) &serv);
+ CHECK_FALSE(held_down);
+}
+
+TEST(ServerConnection, remainingHoldDownSecs)
+{
+ struct tacplus_options::tacplus_options_server serv = {};
+ time_t remaining;
+
+ /* Hold down timer is active */
+ ut_set_cur_mono_time(20, 500);
+ serv.hold_down = 15;
+ SET_TIMESPEC_VALS(serv.state.lastTrouble, 14, 21984);
+
+ remaining = tacplus_server_remaining_hold_down_secs(
+ (const struct tacplus_options_server *) &serv);
+ LONGS_EQUAL(9, remaining);
+
+ /* Hold down timer increases */
+ serv.hold_down += 30;
+ ut_set_cur_mono_time(24, 98897);
+
+ remaining = tacplus_server_remaining_hold_down_secs(
+ (const struct tacplus_options_server *) &serv);
+ LONGS_EQUAL(35, remaining);
+
+ /* Hold down timer decreases */
+ serv.hold_down -= 21;
+ ut_set_cur_mono_time(26, 71298);
+
+ remaining = tacplus_server_remaining_hold_down_secs(
+ (const struct tacplus_options_server *) &serv);
+ LONGS_EQUAL(12, remaining);
+
+ /* Hold down timer disabled */
+ serv.hold_down = 0;
+
+ remaining = tacplus_server_remaining_hold_down_secs(
+ (const struct tacplus_options_server *) &serv);
+ LONGS_EQUAL(0, remaining);
+}
+
+TEST(ServerConnection, copyServerState)
+{
+ struct tacplus_options *new_opts;
+
+ /* Set state on the current opts servers */
+ SET_TIMESPEC_VALS(opts->server[0].state.lastTrouble, 10, 5);
+ SET_TIMESPEC_VALS(opts->server[1].state.lastTrouble, -1, -1);
+ SET_TIMESPEC_VALS(opts->server[2].state.lastTrouble, 767, 9867);
+
+ /* Set some config - this should not be transferred */
+ opts->server[0].hold_down = 10;
+ opts->server[1].hold_down = 15;
+ opts->server[2].hold_down = 20;
+
+ /* Exactly the same servers remain in the new opts */
+ new_opts = tacplus_options_alloc(num_servers);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "2.2.2.2", "2");
+ SET_OPTS_SERVER(new_opts, 2, "3:3::3:3", "3");
+
+ CHECK_TIMESPEC_VALS(new_opts->server[0].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[1].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[2].state.lastTrouble, -1, -1);
+
+ tacplus_copy_server_state(opts, new_opts);
+
+ CHECK_TIMESPEC_VALS(new_opts->server[0].state.lastTrouble, 10, 5);
+ CHECK_TIMESPEC_VALS(new_opts->server[1].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[2].state.lastTrouble, 767, 9867);
+ CHECK_TIMESPEC_VALS(opts->server[0].state.lastTrouble, 10, 5);
+ CHECK_TIMESPEC_VALS(opts->server[1].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(opts->server[2].state.lastTrouble, 767, 9867);
+
+ CHECK_EQUAL(new_opts->server[0].hold_down, 0);
+ CHECK_EQUAL(new_opts->server[1].hold_down, 0);
+ CHECK_EQUAL(new_opts->server[2].hold_down, 0);
+}
+
+TEST(ServerConnection, copyServerStateSomeMatch)
+{
+ struct tacplus_options *new_opts;
+
+ /* Set state on the current opts servers */
+ SET_TIMESPEC_VALS(opts->server[0].state.lastTrouble, 10, 5);
+ SET_TIMESPEC_VALS(opts->server[1].state.lastTrouble, -1, -1);
+ SET_TIMESPEC_VALS(opts->server[2].state.lastTrouble, 767, 9867);
+
+ /* Set some config - this should not be transferred */
+ opts->server[0].hold_down = 10;
+ opts->server[1].hold_down = 15;
+ opts->server[2].hold_down = 20;
+
+ /* Only server ID 0 remains the same in the new opts */
+ new_opts = tacplus_options_alloc(2);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "3.3.3.3", "3");
+
+ CHECK_TIMESPEC_VALS(new_opts->server[0].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[1].state.lastTrouble, -1, -1);
+
+ tacplus_copy_server_state(opts, new_opts);
+
+ CHECK_TIMESPEC_VALS(new_opts->server[0].state.lastTrouble, 10, 5);
+ CHECK_TIMESPEC_VALS(new_opts->server[1].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(opts->server[0].state.lastTrouble, 10, 5);
+ CHECK_TIMESPEC_VALS(opts->server[1].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(opts->server[2].state.lastTrouble, 767, 9867);
+
+ CHECK_EQUAL(new_opts->server[0].hold_down, 0);
+ CHECK_EQUAL(new_opts->server[1].hold_down, 0);
+}
+
+TEST(ServerConnection, copyServerStateAdded)
+{
+ struct tacplus_options *new_opts;
+
+ /* Set state on the current opts servers */
+ SET_TIMESPEC_VALS(opts->server[0].state.lastTrouble, 10, 5);
+ SET_TIMESPEC_VALS(opts->server[1].state.lastTrouble, 0, 2);
+ SET_TIMESPEC_VALS(opts->server[2].state.lastTrouble, 767, 9867);
+
+ /* Set some config - this should not be transferred */
+ opts->server[0].hold_down = 0;
+ opts->server[1].hold_down = 15;
+ opts->server[2].hold_down = 20;
+
+ /* Add an additional server to new opts */
+ new_opts = tacplus_options_alloc(4);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "2.2.2.2", "200"); // Port does not affect match
+ SET_OPTS_SERVER(new_opts, 2, "3:3::3:3", "33");
+ SET_OPTS_SERVER(new_opts, 3, "4.4.4.4", "4");
+
+ CHECK_TIMESPEC_VALS(new_opts->server[0].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[1].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[2].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(new_opts->server[3].state.lastTrouble, -1, -1);
+
+ tacplus_copy_server_state(opts, new_opts);
+
+ /* Hold down not was not previously configured - so last trouble should not be transferred */
+ CHECK_TIMESPEC_VALS(new_opts->server[0].state.lastTrouble, -1, -1);
+
+ CHECK_TIMESPEC_VALS(new_opts->server[1].state.lastTrouble, 0, 2);
+ CHECK_TIMESPEC_VALS(new_opts->server[2].state.lastTrouble, 767, 9867);
+ CHECK_TIMESPEC_VALS(new_opts->server[3].state.lastTrouble, -1, -1);
+ CHECK_TIMESPEC_VALS(opts->server[0].state.lastTrouble, 10, 5);
+ CHECK_TIMESPEC_VALS(opts->server[1].state.lastTrouble, 0, 2);
+ CHECK_TIMESPEC_VALS(opts->server[2].state.lastTrouble, 767, 9867);
+
+ CHECK_EQUAL(new_opts->server[0].hold_down, 0);
+ CHECK_EQUAL(new_opts->server[1].hold_down, 0);
+ CHECK_EQUAL(new_opts->server[2].hold_down, 0);
+ CHECK_EQUAL(new_opts->server[3].hold_down, 0);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsNull)
+{
+ struct tacplus_options *empty = NULL;
+
+ POINTERS_EQUAL(NULL, tacplus_reload_options(&opts, NULL));
+ POINTERS_EQUAL(NULL, opts);
+
+ POINTERS_EQUAL(NULL, tacplus_reload_options(&empty, NULL));
+ POINTERS_EQUAL(NULL, empty);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigWithTrouble)
+{
+ struct tacplus_options *opts_one, *opts_two, *opts_three;
+
+ opts_one = tacplus_options_alloc(1);
+ CHECK(opts_one);
+
+ SET_OPTS_SERVER(opts_one, 0, "1.1.1.1", "1");
+
+ /* Current server experienced trouble */
+ tacplus_server_activate_hold_down(
+ (struct tacplus_options_server *)&opts->server[0]);
+ LONGS_EQUAL(0, opts->server[0].hold_down);
+
+ /* Configure a hold down timer and reload */
+ opts_one->server[0].hold_down = 10;
+ ut_inc_cur_mono_time(3, 0);
+ POINTERS_EQUAL(opts_one, tacplus_reload_options(&opts, opts_one));
+ POINTERS_EQUAL(NULL, opts);
+
+ /* Hold down timer should not be activated */
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts_one->server[0]));
+
+ /* Unless we experience some more trouble on the server */
+ ut_inc_cur_mono_time(2, 0);
+ tacplus_server_activate_hold_down(
+ (struct tacplus_options_server *)&opts_one->server[0]);
+
+ CHECK(tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts_one->server[0]));
+ LONGS_EQUAL(10, tacplus_server_remaining_hold_down_secs(
+ (struct tacplus_options_server *)&opts_one->server[0]));
+
+ /* Reconfigure with disabled hold down timers */
+ opts_two = tacplus_options_alloc(1);
+ CHECK(opts_two);
+
+ SET_OPTS_SERVER(opts_two, 0, "1.1.1.1", "1");
+
+ LONGS_EQUAL(0, opts_two->server[0].hold_down);
+
+ POINTERS_EQUAL(opts_two, tacplus_reload_options(&opts_one, opts_two));
+ POINTERS_EQUAL(NULL, opts_one);
+
+ /* Hold down timer should not be activated */
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts_two->server[0]));
+
+ /* Reconfigure with hold down timer once again */
+ opts_three = tacplus_options_alloc(1);
+
+ SET_OPTS_SERVER(opts_three, 0, "1.1.1.1", "1");
+ opts_three->server[0].hold_down = 3000;
+
+ POINTERS_EQUAL(opts_three, tacplus_reload_options(&opts_two, opts_three));
+ POINTERS_EQUAL(NULL, opts_two);
+
+ /* Hold down timer should not be activated */
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts_three->server[0]));
+
+ cleanup_tacplus_options(&opts_three);
+ POINTERS_EQUAL(NULL, opts_three);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigWithoutTrouble)
+{
+ struct tacplus_options *new_opts;
+
+ new_opts = tacplus_options_alloc(2);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "3:3::3:3", "3");
+
+ LONGS_EQUAL(0, opts->server[0].hold_down);
+
+ /* Configure a hold down timer and reload */
+ new_opts->server[0].hold_down = 10;
+ ut_inc_cur_mono_time(3, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* Hold down timer should not be activated */
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&new_opts->server[0]));
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDisable)
+{
+ struct tacplus_options *new_opts;
+
+ new_opts = tacplus_options_alloc(2);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "3:3::3:3", "3");
+
+ LONGS_EQUAL(0, new_opts->server[0].hold_down);
+ LONGS_EQUAL(0, new_opts->server[1].hold_down);
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 15;
+ opts->server[1].hold_down = 20;
+
+ tacplus_server_activate_hold_down(
+ (struct tacplus_options_server *)&opts->server[0]);
+
+ CHECK(tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts->server[0]));
+
+ ut_inc_cur_mono_time(10, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* Hold down timers should not be activated */
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&new_opts->server[0]));
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&new_opts->server[1]));
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigIncrement)
+{
+ struct tacplus_options *new_opts;
+
+ new_opts = tacplus_options_alloc(2);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "3:3::3:3", "3");
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 15;
+ opts->server[1].hold_down = 20;
+
+ /* Increase hold down of new config */
+ new_opts->server[0].hold_down = 20;
+ new_opts->server[1].hold_down = 25;
+
+ tacplus_server_activate_hold_down(
+ (struct tacplus_options_server *)&opts->server[0]);
+
+ LONGS_EQUAL(15, tacplus_server_remaining_hold_down_secs(
+ (struct tacplus_options_server *)&opts->server[0]));
+
+ ut_inc_cur_mono_time(10, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ LONGS_EQUAL(10, tacplus_server_remaining_hold_down_secs(
+ (struct tacplus_options_server *)&new_opts->server[0]));
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDecrement)
+{
+ struct tacplus_options *new_opts;
+
+ new_opts = tacplus_options_alloc(2);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "3:3::3:3", "3");
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 15;
+ opts->server[1].hold_down = 20;
+
+ /* Decrease hold down of new config */
+ new_opts->server[0].hold_down = 10;
+ new_opts->server[1].hold_down = 15;
+
+ tacplus_server_activate_hold_down(
+ (struct tacplus_options_server *)&opts->server[0]);
+
+ LONGS_EQUAL(15, tacplus_server_remaining_hold_down_secs(
+ (struct tacplus_options_server *)&opts->server[0]));
+
+ ut_inc_cur_mono_time(5, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ LONGS_EQUAL(5, tacplus_server_remaining_hold_down_secs(
+ (struct tacplus_options_server *)&new_opts->server[0]));
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDecrementBelowRemaining)
+{
+ struct tacplus_options *new_opts;
+
+ new_opts = tacplus_options_alloc(2);
+ CHECK(new_opts);
+
+ SET_OPTS_SERVER(new_opts, 0, "1.1.1.1", "1");
+ SET_OPTS_SERVER(new_opts, 1, "3:3::3:3", "3");
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 15;
+ opts->server[1].hold_down = 20;
+
+ /* Decrease hold down of new config */
+ new_opts->server[0].hold_down = 10;
+ new_opts->server[1].hold_down = 15;
+
+ tacplus_server_activate_hold_down(
+ (struct tacplus_options_server *)&opts->server[0]);
+
+ CHECK(tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts->server[0]));
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&opts->server[1]));
+
+ ut_inc_cur_mono_time(12, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&new_opts->server[0]));
+ CHECK(! tacplus_server_is_held_down(
+ (struct tacplus_options_server *)&new_opts->server[1]));
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsNoHoldDownConfigOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Verify no hold down on current and new config */
+ for (unsigned i = 0; i < opts->n_servers; i++) {
+ LONGS_EQUAL(0, opts->server[i].hold_down);
+ LONGS_EQUAL(0, new_opts->server[i].hold_down);
+ }
+
+ /* No hold down - we should be online */
+ CHECK_TRUE(tacplusd_online());
+
+ /* Pass some time and reload */
+ ut_inc_cur_mono_time(10, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* Still No hold down - we should still be online */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Verify no hold down on current config */
+ for (unsigned i = 0; i < opts->n_servers; i++)
+ LONGS_EQUAL(0, opts->server[i].hold_down);
+
+ /* Add hold down to new config */
+ new_opts->server[0].hold_down = 10;
+ new_opts->server[1].hold_down = 15;
+ new_opts->server[2].hold_down = 20;
+
+ /* No hold down - we should be online */
+ CHECK_TRUE(tacplusd_online());
+
+ /* Pass some time and reload */
+ ut_inc_cur_mono_time(5, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* Now have hold down but no trouble - we should still be online */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+static void ut_do_failing_tacplus_connect(
+ struct tacplus_options *opts, bool enforce_go_offline)
+{
+ int *fds = (int *) calloc(opts->n_servers, sizeof(int));
+ CHECK(fds);
+
+ unsigned num_servers_with_hold_down = 0;
+ for (unsigned i = 0; i < opts->n_servers; i++) {
+ if (opts->server[i].hold_down > 0)
+ num_servers_with_hold_down++;
+ fds[i] = -1;
+ }
+ ut_set_tac_connect_fds(fds, opts->n_servers);
+
+ if (enforce_go_offline) {
+ LONGS_EQUAL_TEXT(opts->n_servers, num_servers_with_hold_down,
+ "Test requires a hold down on all servers");
+ }
+ else {
+ CHECK_TEXT(num_servers_with_hold_down < opts->n_servers,
+ "Test requires no hold down on at least one server");
+ }
+
+ CHECK_FALSE(tacplus_connect());
+ CHECK_TEXT(tacplusd_online() == !enforce_go_offline,
+ "Unexpected online state");
+
+ ut_reset_tac_connect_wrapper();
+ free(fds);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigWithTroubleOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Verify no hold down on current config */
+ for (unsigned i = 0; i < opts->n_servers; i++)
+ LONGS_EQUAL(0, opts->server[i].hold_down);
+
+ /* Add hold down to new config */
+ new_opts->server[0].hold_down = 10;
+ new_opts->server[1].hold_down = 15;
+ new_opts->server[2].hold_down = 20;
+
+ /* Experience trouble on all servers */
+ ut_do_failing_tacplus_connect(opts, false);
+
+ /* No hold down - we should be online despite trouble */
+ CHECK_TRUE(tacplusd_online());
+
+ /* Pass some time and reload */
+ ut_inc_cur_mono_time(5, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /*
+ * We should still be online - previous trouble is ignored on a
+ * newly configured hold down timer.
+ */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDisableOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 10;
+ opts->server[1].hold_down = 15;
+ opts->server[2].hold_down = 20;
+
+ /* Verify no hold down on new config */
+ for (unsigned i = 0; i < new_opts->n_servers; i++)
+ LONGS_EQUAL(0, new_opts->server[i].hold_down);
+
+ /* Go offline */
+ ut_do_failing_tacplus_connect(opts, true);
+
+ /* Pass some time (but still within offline period) and reload */
+ ut_inc_cur_mono_time(5, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* Hold down was removed - we should be online again */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDisableOneOnlineCheck)
+{
+ struct tacplus_options *new_opts;
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 20;
+ opts->server[1].hold_down = 25;
+ opts->server[2].hold_down = 30;
+
+ new_opts = copy_tacplus_options(opts);
+
+ /* Remove hold down from second server in new config */
+ new_opts->server[1].hold_down = 0;
+ LONGS_EQUAL(20, new_opts->server[0].hold_down);
+ LONGS_EQUAL(30, new_opts->server[2].hold_down);
+
+ /* Go offline */
+ ut_do_failing_tacplus_connect(opts, true);
+
+ /* Pass some time (but still within offline period) and reload */
+ ut_inc_cur_mono_time(15, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* We should be online again since server 2 is now available */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigIncrementOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 10;
+ opts->server[1].hold_down = 15;
+ opts->server[2].hold_down = 20;
+
+ /* Increase hold down on new config */
+ new_opts->server[0].hold_down = 20;
+ new_opts->server[1].hold_down = 25;
+ new_opts->server[2].hold_down = 30;
+
+ /* Go offline */
+ ut_do_failing_tacplus_connect(opts, true);
+
+ /* Pass some time (but still within offline period) and reload */
+ ut_inc_cur_mono_time(5, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* We should still be offline */
+ CHECK_FALSE(tacplusd_online());
+
+ /* Pass enough time that the original hold down would have expired */
+ ut_inc_cur_mono_time(6, 0);
+
+ /* We should still be offline */
+ CHECK_FALSE(tacplusd_online());
+
+ /* Pass enough time that the first server hold down expires */
+ ut_inc_cur_mono_time(10, 0);
+
+ /* We should be online again */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDecrementOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 20;
+ opts->server[1].hold_down = 25;
+ opts->server[2].hold_down = 30;
+
+ /* Decrease hold down on new config */
+ new_opts->server[0].hold_down = 10;
+ new_opts->server[1].hold_down = 15;
+ new_opts->server[2].hold_down = 20;
+
+ /* Go offline */
+ ut_do_failing_tacplus_connect(opts, true);
+
+ /* Pass some time (but still within offline period) and reload */
+ ut_inc_cur_mono_time(5, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* We should still be offline */
+ CHECK_FALSE(tacplusd_online());
+
+ /* Pass enough time that the new hold down expires */
+ ut_inc_cur_mono_time(6, 0);
+
+ /* We should be online again */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
+
+TEST(ServerConnection, tacplusReloadOptionsHoldDownConfigDecrementBelowRemainingOnlineCheck)
+{
+ struct tacplus_options *new_opts = copy_tacplus_options(opts);
+
+ /* Add hold down to current config */
+ opts->server[0].hold_down = 20;
+ opts->server[1].hold_down = 25;
+ opts->server[2].hold_down = 30;
+
+ /* Decrease hold down on new config */
+ new_opts->server[0].hold_down = 10;
+ new_opts->server[1].hold_down = 15;
+ new_opts->server[2].hold_down = 20;
+
+ /* Go offline */
+ ut_do_failing_tacplus_connect(opts, true);
+
+ /* Pass enough time that the new hold down expires */
+ ut_inc_cur_mono_time(15, 0);
+ POINTERS_EQUAL(new_opts, tacplus_reload_options(&opts, new_opts));
+
+ /* We should be online again */
+ CHECK_TRUE(tacplusd_online());
+
+ cleanup_tacplus_options(&new_opts);
+ POINTERS_EQUAL(NULL, new_opts);
+}
diff --git a/tacplus-daemon/test/testMain.cpp b/tacplus-daemon/test/testMain.cpp
new file mode 100644
index 0000000..c7ea462
--- /dev/null
+++ b/tacplus-daemon/test/testMain.cpp
@@ -0,0 +1,19 @@
+/*
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "CppUTest/CommandLineTestRunner.h"
+extern "C" {
+ #include "global.h"
+}
+
+static ConnectionControl _connControl;
+ConnectionControl *connControl = &_connControl;
+
+int main(int ac, char** av)
+{
+ return CommandLineTestRunner::RunAllTests(ac, av);
+}
diff --git a/tacplus-daemon/test/transactionTester.cpp b/tacplus-daemon/test/transactionTester.cpp
new file mode 100644
index 0000000..3c16ae0
--- /dev/null
+++ b/tacplus-daemon/test/transactionTester.cpp
@@ -0,0 +1,276 @@
+/*
+ Copyright (c) 2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "CppUTest/TestHarness.h"
+extern "C" {
+ #include <libtac.h>
+ #include "transaction.h"
+ #include "transaction_private.h"
+ #include "utils.h"
+}
+#include "ut_utils.h"
+
+TEST_GROUP(Transaction) {};
+
+TEST(Transaction, NewAttribInvalid)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new(NULL);
+ POINTERS_EQUAL(NULL, attr);
+
+ attr = transaction_attrib_new("");
+ POINTERS_EQUAL(NULL, attr);
+
+ attr = transaction_attrib_new("=");
+ POINTERS_EQUAL(NULL, attr);
+
+ attr = transaction_attrib_new("*");
+ POINTERS_EQUAL(NULL, attr);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribNoSep)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("foobarbaz");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("foobarbaz=", attr->name);
+ STRCMP_EQUAL("", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribShortNoSep)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("f");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("f=", attr->name);
+ STRCMP_EQUAL("", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribMandatory)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("cmd=set foo");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("cmd=", attr->name);
+ STRCMP_EQUAL("set foo", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribMandatoryShortNoVal)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("c=");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("c=", attr->name);
+ STRCMP_EQUAL("", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribOptional)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("cmd*set foo");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("cmd*", attr->name);
+ STRCMP_EQUAL("set foo", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribOptionalShortNoVal)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("c*");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("c*", attr->name);
+ STRCMP_EQUAL("", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribMandatoryWithOptSepInValue)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("cmd=set f*o");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("cmd=", attr->name);
+ STRCMP_EQUAL("set f*o", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribOptionalWithMandSepInValue)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("cmd*set foo=bar");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("cmd*", attr->name);
+ STRCMP_EQUAL("set foo=bar", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, NewAttribMulti)
+{
+ struct transaction_attrib *attr;
+
+ attr = transaction_attrib_new("cmd*set foo");
+ CHECK(attr != NULL);
+
+ STRCMP_EQUAL("cmd*", attr->name);
+ STRCMP_EQUAL("set foo", attr->value);
+ POINTERS_EQUAL(NULL, attr->next);
+
+ attr->next = transaction_attrib_new("cmd=delete foo");
+ CHECK(attr->next != NULL);
+
+ STRCMP_EQUAL("cmd=", attr->next->name);
+ STRCMP_EQUAL("delete foo", attr->next->value);
+ POINTERS_EQUAL(NULL, attr->next->next);
+
+ transaction_attrib_free(&attr);
+ POINTERS_EQUAL(NULL, attr);
+};
+
+TEST(Transaction, TransactionAttribFromTacAttribNull)
+{
+ POINTERS_EQUAL(NULL, transaction_attrib_from_tac_attrib(NULL));
+}
+
+static void compare_transaction_attribs(struct transaction_attrib exp[],
+ unsigned exp_len,
+ struct transaction_attrib *act)
+{
+ unsigned i;
+ for (i = 0; act; act = act->next, i++) {
+ STRCMP_EQUAL(exp[i].name, act->name);
+ STRCMP_EQUAL(exp[i].value, act->value);
+
+ if (act->next == NULL) {
+ LONGS_EQUAL_TEXT(
+ exp_len-1, i, "transaction_attrib list unexpectedly short");
+ }
+ else {
+ CHECK_TEXT(
+ i < exp_len-1, "transaction_attrib list unexpectedly long");
+ }
+ }
+
+ /* Final sanity check */
+ LONGS_EQUAL_TEXT(exp_len, i, "transaction_attrib list length mismatch");
+}
+
+TEST(Transaction, TransactionAttribFromTacAttribSingle)
+{
+ struct tac_attrib *tac_attrib = NULL;
+
+ tac_add_attrib(&tac_attrib, (char *)"cmd", (char *)"set foo bar");
+ CHECK(tac_attrib != NULL);
+
+ struct transaction_attrib *attrib;
+ attrib = transaction_attrib_from_tac_attrib(tac_attrib);
+ CHECK(attrib != NULL);
+
+ struct transaction_attrib exp_attrib[] = {
+ {
+ .next = NULL,
+ .name = "cmd=",
+ .value = "set foo bar",
+ },
+ };
+ compare_transaction_attribs(exp_attrib, ARRAY_SIZE(exp_attrib), attrib);
+
+ transaction_attrib_free(&attrib);
+ POINTERS_EQUAL(NULL, attrib);
+}
+
+TEST(Transaction, TransactionAttribFromTacAttribMulti)
+{
+ struct tac_attrib *tac_attrib = NULL;
+
+ tac_add_attrib(&tac_attrib, (char *)"cmd", (char *)"set");
+ CHECK(tac_attrib != NULL);
+ tac_add_attrib(&tac_attrib, (char *)"cmd-arg", (char *)"foo");
+ tac_add_attrib(&tac_attrib, (char *)"cmd-arg", (char *)"bar");
+ tac_add_attrib(&tac_attrib, (char *)"cmd-arg", (char *)"baz");
+
+ struct transaction_attrib *attrib;
+ attrib = transaction_attrib_from_tac_attrib(tac_attrib);
+ CHECK(attrib != NULL);
+
+ /*
+ * "next" member is unused by the tests, it is initialised to
+ * silence the C++ compiler.
+ */
+ struct transaction_attrib exp_attrib[] = {
+ {
+ .next = NULL,
+ .name = "cmd=",
+ .value = "set",
+ },
+ {
+ .next = NULL,
+ .name = "cmd-arg=",
+ .value = "foo",
+ },
+ {
+ .next = NULL,
+ .name = "cmd-arg=",
+ .value = "bar",
+ },
+ {
+ .next = NULL,
+ .name = "cmd-arg=",
+ .value = "baz",
+ },
+ };
+ compare_transaction_attribs(exp_attrib, ARRAY_SIZE(exp_attrib), attrib);
+
+ transaction_attrib_free(&attrib);
+ POINTERS_EQUAL(NULL, attrib);
+}
diff --git a/tacplus-daemon/test/ut_utils.c b/tacplus-daemon/test/ut_utils.c
new file mode 100644
index 0000000..107656d
--- /dev/null
+++ b/tacplus-daemon/test/ut_utils.c
@@ -0,0 +1,17 @@
+/*
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "ut_utils.h"
+
+int *_tac_connect_fds = NULL;
+int _tac_connect_fds_len = 0;
+
+struct tac_connect_call *_tac_connect_calls = NULL;
+int _tac_connect_call_count = 0;
+
+struct timespec _cur_time = { 0, 0 };
+
+struct timespec _offline_until = { 0, 0 };
diff --git a/tacplus-daemon/test/ut_utils.h b/tacplus-daemon/test/ut_utils.h
new file mode 100644
index 0000000..b16e4b5
--- /dev/null
+++ b/tacplus-daemon/test/ut_utils.h
@@ -0,0 +1,189 @@
+/*
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <assert.h>
+#include <netdb.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "utils.h"
+
+#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
+
+extern int *_tac_connect_fds;
+extern int _tac_connect_fds_len;
+
+struct tac_connect_call {
+ struct sockaddr server_addr;
+ struct sockaddr source_addr;
+ const char *key;
+ int timeout;
+};
+
+extern struct tac_connect_call *_tac_connect_calls;
+extern int _tac_connect_call_count;
+
+bool
+ut_tac_connect_call_eq(struct tac_connect_call *a, struct tac_connect_call *b)
+{
+ if (a->timeout != b->timeout)
+ return false;
+
+ if (a->key && b->key) {
+ if (strcmp(a->key, b->key) != 0)
+ return false;
+ }
+ else if (! (!a->key && !b->key)) {
+ return false;
+ }
+
+ if (memcmp(&a->server_addr, &b->server_addr, sizeof a->server_addr))
+ return false;
+
+ if (memcmp(&a->source_addr, &b->source_addr, sizeof a->source_addr))
+ return false;
+
+ return true;
+}
+
+void
+ut_reset_tac_connect_wrapper()
+{
+ for (int i = 0; i < _tac_connect_fds_len; i++)
+ free((void *)_tac_connect_calls[i].key);
+
+ free(_tac_connect_calls);
+ _tac_connect_calls = NULL;
+ _tac_connect_call_count = 0;
+
+ _tac_connect_fds = NULL;
+ _tac_connect_fds_len = 0;
+}
+
+void
+ut_set_tac_connect_fds(int *fds, int fds_len)
+{
+ _tac_connect_fds = fds;
+ _tac_connect_fds_len = fds_len;
+
+ assert(! _tac_connect_calls);
+ _tac_connect_calls = (struct tac_connect_call *) calloc(_tac_connect_fds_len,
+ sizeof(struct tac_connect_call));
+ assert(_tac_connect_calls);
+}
+
+int
+ut_get_tac_connect_calls(struct tac_connect_call **calls)
+{
+ *calls = _tac_connect_calls;
+ return _tac_connect_call_count;
+}
+
+/*
+ * Wrapper function for tac_connect_single()
+ *
+ * Use ut_set_tac_connect_fds() to set an array of return values for this
+ * function.
+ *
+ * Use ut_get_tac_connect_calls() to retrieve an array of tac_connect_call
+ * structs with the arguments from each call to tac_connect_single().
+ */
+int
+__wrap_tac_connect_single(const struct addrinfo *server,
+ const char *key,
+ struct addrinfo *srcaddr,
+ int timeout)
+{
+ if (! _tac_connect_fds)
+ return 9999;
+
+ assert(_tac_connect_call_count < _tac_connect_fds_len);
+
+ struct tac_connect_call *call = &_tac_connect_calls[_tac_connect_call_count];
+ if (server)
+ call->server_addr = *server->ai_addr;
+
+ if (srcaddr)
+ call->source_addr = *srcaddr->ai_addr;
+
+ call->key = key ? strdup(key) : NULL;
+ call->timeout = timeout;
+
+ return _tac_connect_fds[_tac_connect_call_count++];
+}
+
+#define CHECK_TIMESPEC_VALS(T,S,N) \
+ { \
+ LONGS_EQUAL(S, T.tv_sec); \
+ LONGS_EQUAL(N, T.tv_nsec); \
+ }
+
+extern struct timespec _cur_time;
+
+void
+__wrap_cur_mono_time(struct timespec *ts)
+{
+ *ts = _cur_time;
+}
+
+void
+ut_set_cur_mono_time(time_t sec, long nsec)
+{
+ _cur_time.tv_sec = sec;
+ _cur_time.tv_nsec = nsec;
+}
+
+void
+ut_inc_cur_mono_time(time_t sec, long nsec)
+{
+ ut_set_cur_mono_time(_cur_time.tv_sec + sec,
+ _cur_time.tv_nsec + nsec);
+}
+
+/*
+ * Wrapper around the online/offline APIs
+ *
+ * Piggybacking on the simulated UT "time" (_cur_time) gives a simple mechanism
+ * to simulate the service going on and offline in UTs.
+ *
+ * Upon request to go offline we store the "time" we expect to go back online
+ * ie. the current UT "time" plus the offline interval.
+ *
+ * Conversely on a request to go online we store the current UT "time".
+ *
+ * To determine whether we are on or offline we then simply check whether
+ * the current UT "time" is past the stored time (ie. when we expected to be
+ * offline until).
+ */
+extern struct timespec _offline_until;
+
+bool
+__wrap_tacplusd_go_online() {
+ _offline_until = _cur_time;
+ return true;
+}
+
+bool
+__wrap_tacplusd_go_offline(const struct timespec *ts) {
+ struct timespec ts_copy = *ts;
+ timespec_normalise(&ts_copy);
+
+ _offline_until = _cur_time;
+ timespec_normalise(&_offline_until);
+
+ _offline_until.tv_sec += ts_copy.tv_sec;
+ _offline_until.tv_nsec += ts_copy.tv_nsec;
+ timespec_normalise(&_offline_until);
+
+ return true;
+}
+
+bool
+__wrap_tacplusd_online() {
+ return timespec_cmp(&_cur_time, &_offline_until) >= 0;
+}
diff --git a/tacplus-daemon/test/utilsTester.cpp b/tacplus-daemon/test/utilsTester.cpp
new file mode 100644
index 0000000..c2ae92f
--- /dev/null
+++ b/tacplus-daemon/test/utilsTester.cpp
@@ -0,0 +1,324 @@
+/*
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include "CppUTest/TestHarness.h"
+extern "C" {
+ #include "utils.h"
+}
+#include "ut_utils.h"
+
+TEST_GROUP(Utils) {};
+
+TEST(Utils, tacplusAddrInfoV4)
+{
+ struct addrinfo *addr;
+ char addr_str[INET_ADDRSTRLEN];
+ char port_str[6];
+ int ret;
+
+ addr = tacplus_addrinfo("192.168.122.7", "66");
+ CHECK(addr != NULL);
+
+ ret = getnameinfo(addr->ai_addr, addr->ai_addrlen,
+ addr_str, INET_ADDRSTRLEN,
+ port_str, sizeof port_str,
+ NI_NUMERICHOST|NI_NUMERICSERV);
+ CHECK(ret == 0);
+
+ STRCMP_EQUAL("192.168.122.7", addr_str);
+ STRCMP_EQUAL("66", port_str);
+
+ freeaddrinfo(addr);
+};
+
+TEST(Utils, tacplusAddrInfoV6)
+{
+ struct addrinfo *addr;
+ char addr_str[INET6_ADDRSTRLEN];
+ char port_str[6];
+ int ret;
+
+ addr = tacplus_addrinfo("fe80::177", "49");
+ CHECK(addr != NULL);
+
+ ret = getnameinfo(addr->ai_addr, addr->ai_addrlen,
+ addr_str, INET6_ADDRSTRLEN,
+ port_str, sizeof port_str,
+ NI_NUMERICHOST|NI_NUMERICSERV);
+ CHECK(ret == 0);
+
+ STRCMP_EQUAL("fe80::177", addr_str);
+ STRCMP_EQUAL("49", port_str);
+
+ freeaddrinfo(addr);
+};
+
+TEST(Utils, tacplusAddrInfoInvalid)
+{
+ struct addrinfo *addr;
+
+ addr = tacplus_addrinfo("invalid address", "invalid port");
+ POINTERS_EQUAL(NULL, addr);
+};
+
+TEST(Utils, strOrNil)
+{
+ const char *str;
+
+ str = strOrNil(NULL);
+ CHECK_EQUAL("(nil)", str);
+
+ str = strOrNil("foo");
+ STRCMP_EQUAL("foo", str);
+}
+
+TEST(Utils, addrinfoToString)
+{
+ struct addrinfo *info;
+ char *addr_str;
+
+ info = tacplus_addrinfo("1.1.1.1", "50");
+ CHECK(info);
+ addr_str = addrinfo_to_string(info);
+ STRCMP_EQUAL("1.1.1.1", addr_str);
+ freeaddrinfo(info);
+ free(addr_str);
+
+ info = tacplus_addrinfo("2:2::2:2", "55");
+ CHECK(info);
+ addr_str = addrinfo_to_string(info);
+ STRCMP_EQUAL("2:2::2:2", addr_str);
+ freeaddrinfo(info);
+ free(addr_str);
+}
+
+TEST(Utils, getAddrinfoPort)
+{
+ struct addrinfo *info;
+ uint16_t port;
+
+ info = tacplus_addrinfo("1.1.1.1", "50");
+ CHECK(info);
+ port = get_addrinfo_port(info);
+ CHECK_EQUAL(50, port);
+ freeaddrinfo(info);
+
+ info = tacplus_addrinfo("2:2::2:2", "65535");
+ CHECK(info);
+ port = get_addrinfo_port(info);
+ CHECK_EQUAL(65535, port);
+ freeaddrinfo(info);
+}
+
+TEST(Utils, timespecValsEq)
+{
+ struct timespec ts = { 10, 908987 };
+ CHECK(TIMESPEC_VALS_EQ(ts, 10, 908987));
+}
+
+TEST(Utils, setTimespecVals)
+{
+ struct timespec ts = {};
+ CHECK(TIMESPEC_VALS_EQ(ts, 0, 0));
+
+ SET_TIMESPEC_VALS(ts, 55, 9824798);
+ CHECK(TIMESPEC_VALS_EQ(ts, 55, 9824798));
+}
+
+/*
+ * timespec_normalise() tests
+ */
+
+TEST(Utils, timespecNormaliseNoOp)
+{
+ struct timespec ts = { 1, 500 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, 1, 500);
+}
+
+TEST(Utils, timespecNormaliseMultiWholeSec)
+{
+ struct timespec ts = { 890, 10000000000 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, 900, 0);
+}
+
+TEST(Utils, timespecNormaliseMultiSec)
+{
+ struct timespec ts = { 123210, 6674000000 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, 123216, 674000000);
+}
+
+TEST(Utils, timespecNormaliseNeg)
+{
+ struct timespec ts = { 67, -234 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, 66, 999999766);
+}
+
+TEST(Utils, timespecNormaliseMultiWholeNegSec)
+{
+ struct timespec ts = { 27, -4000000000 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, 23, 0);
+}
+
+TEST(Utils, timespecNormaliseMultiNegSec)
+{
+ struct timespec ts = { 12, -3000004413 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, 8, 999995587);
+}
+
+
+TEST(Utils, timespecNormaliseNegSecNoOp)
+{
+ struct timespec ts = { -1, 500 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, -1, 500);
+}
+
+TEST(Utils, timespecNormaliseNegMultiWholeSec)
+{
+ struct timespec ts = { -890, 10000000000 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, -880, 0);
+}
+
+TEST(Utils, timespecNormaliseNegMultiSec)
+{
+ struct timespec ts = { -123210, 6540674000 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, -123204, 540674000);
+}
+
+TEST(Utils, timespecNormaliseNegNeg)
+{
+ struct timespec ts = { -67, -234 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, -68, 999999766);
+}
+
+TEST(Utils, timespecNormaliseNegMultiWholeNegSec)
+{
+ struct timespec ts = { -27, -4000000000 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, -31, 0);
+}
+
+TEST(Utils, timespecNormaliseNegMultiNegSec)
+{
+ struct timespec ts = { -12, -3097604413 }, *ret;
+
+ ret = timespec_normalise(&ts);
+ POINTERS_EQUAL(&ts, ret);
+ CHECK_TIMESPEC_VALS(ts, -16, 902395587);
+}
+
+/*
+ * timespec_cmp() tests
+ */
+
+TEST(Utils, timespecCmpEq)
+{
+ struct timespec a = { 1, 200 };
+ struct timespec b = a;
+
+ CHECK_EQUAL(timespec_cmp(&a, &b), 0);
+ CHECK_EQUAL(timespec_cmp(&b, &a), 0);
+}
+
+TEST(Utils, timespecCmpDiffSecs)
+{
+ struct timespec a = { 1, 200 };
+ struct timespec b = { 10, 200 };
+
+ CHECK_EQUAL(timespec_cmp(&a, &b), -1);
+ CHECK_EQUAL(timespec_cmp(&b, &a), 1);
+}
+
+TEST(Utils, timespecCmpDiffNsecs)
+{
+ struct timespec a = { 188, 800 };
+ struct timespec b = { 188, 89787 };
+
+ CHECK_EQUAL(timespec_cmp(&a, &b), -1);
+ CHECK_EQUAL(timespec_cmp(&b, &a), 1);
+}
+
+TEST(Utils, timespecCmpDiffSecsNsecs)
+{
+ struct timespec a = { 12, 12786 };
+ struct timespec b = { 90, 82174 };
+
+ CHECK_EQUAL(timespec_cmp(&a, &b), -1);
+ CHECK_EQUAL(timespec_cmp(&b, &a), 1);
+
+ a.tv_nsec = b.tv_nsec + 10;
+ CHECK_EQUAL(timespec_cmp(&a, &b), -1);
+ CHECK_EQUAL(timespec_cmp(&b, &a), 1);
+}
+
+/*
+ * timespec_sub() tests
+ */
+
+TEST(Utils, timespecSubEq)
+{
+ struct timespec a = { 1, 200 };
+ struct timespec b = a;
+ struct timespec res;
+
+ timespec_sub(&a, &b, &res);
+ CHECK_TIMESPEC_VALS(res, 0, 0);
+ CHECK_TIMESPEC_VALS(a, 1, 200);
+ CHECK_TIMESPEC_VALS(b, 1, 200);
+
+ timespec_sub(&b, &a, &res);
+ CHECK_TIMESPEC_VALS(res, 0, 0);
+ CHECK_TIMESPEC_VALS(a, 1, 200);
+ CHECK_TIMESPEC_VALS(b, 1, 200);
+}
+
+TEST(Utils, timespecSub)
+{
+ struct timespec a = { 12, 98200 };
+ struct timespec b = { 4, 999098654 };
+ struct timespec res;
+
+ timespec_sub(&a, &b, &res);
+ CHECK_TIMESPEC_VALS(res, 7, 999546);
+ CHECK_TIMESPEC_VALS(a, 12, 98200);
+ CHECK_TIMESPEC_VALS(b, 4, 999098654);
+
+ timespec_sub(&b, &a, &res);
+ CHECK_TIMESPEC_VALS(res, -8, 999000454);
+ CHECK_TIMESPEC_VALS(a, 12, 98200);
+ CHECK_TIMESPEC_VALS(b, 4, 999098654);
+}
diff --git a/tacplus-daemon/transaction.c b/tacplus-daemon/transaction.c
new file mode 100644
index 0000000..4367b58
--- /dev/null
+++ b/tacplus-daemon/transaction.c
@@ -0,0 +1,519 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <assert.h>
+#include <stdbool.h>
+#include <syslog.h>
+
+#include <libtac.h>
+#include <tacplus.h>
+
+#include "global.h"
+#include "statistics.h"
+#include "tacplus_srv_conn.h"
+#include "transaction.h"
+#include "transaction_private.h"
+
+#define OPTIONAL_ATTR_SEP '*'
+
+const char *transaction_type_str(transaction_type_t type)
+{
+ switch (type) {
+ case TRANSACTION_ACCOUNT:
+ return "accounting";
+ case TRANSACTION_AUTHEN:
+ return "authentication";
+ case TRANSACTION_AUTHOR:
+ return "authorization";
+ case TRANSACTION_INVALID:
+ return "invalid";
+ default:
+ break;
+ }
+
+ syslog(LOG_ERR, "Unknown transaction type %d", type);
+ return "unknown";
+}
+
+struct transaction *transaction_new(transaction_type_t type)
+{
+ struct transaction *t;
+
+ t = calloc(1, sizeof(*t));
+ if (!t)
+ return NULL;
+
+ t->type = type;
+ return t;
+}
+
+void transaction_free(struct transaction **t)
+{
+ if (t && *t) {
+ switch ((*t)->type) {
+ case TRANSACTION_AUTHOR:
+ transaction_attrib_free(&(*t)->response.author.attrs);
+ break;
+ case TRANSACTION_AUTHEN:
+ case TRANSACTION_ACCOUNT:
+ case TRANSACTION_INVALID:
+ break;
+ }
+ free(*t);
+ *t = NULL;
+ }
+}
+
+struct transaction_attrib *
+transaction_attrib_new(const char *av_pair)
+{
+ if (! av_pair || strlen(av_pair) == 0)
+ return NULL;
+
+ /*
+ * We need an attribute name, and separators are not allowed to be part
+ * of the name. Therefore if the first character is a separator just fail.
+ */
+ if (av_pair[0] == '*' || av_pair[0] == '=')
+ return NULL;
+
+ struct transaction_attrib *attr = calloc(1, sizeof(struct transaction_attrib));
+ if (! attr)
+ goto malloc_fail;
+
+ char *mand_sep = strchr(av_pair, '=');
+ char *opt_sep = strchr(av_pair, '*');
+ char *sep = NULL;
+
+ /*
+ * If both separators are found it means that one or both of them is
+ * present in the attribute value as well as the name. Therefore take
+ * the first one which occurs as the separator.
+ */
+ if (mand_sep && opt_sep)
+ sep = mand_sep < opt_sep ? mand_sep : opt_sep;
+ else if (mand_sep)
+ sep = mand_sep;
+ else if (opt_sep)
+ sep = opt_sep;
+
+ if (sep) {
+ attr->name = strndup(av_pair, (sep + 1) - av_pair);
+ attr->value = strdup(sep + 1);
+ }
+ else {
+ /* If there is no separator found treat it as a mandatory attribute */
+ if ((attr->name = calloc(1, strlen(av_pair) + 2))) {
+ strcat((char *) attr->name, av_pair);
+ strcat((char *) attr->name, "=");
+ }
+ attr->value = strdup("");
+ }
+
+ if (attr->name && attr->value)
+ return attr;
+
+malloc_fail:
+ syslog(LOG_ERR, "tacplus_attrib memory allocation failure!");
+ transaction_attrib_free(&attr);
+ return NULL;
+}
+
+void transaction_attrib_free(struct transaction_attrib **head)
+{
+ if (head && *head) {
+ struct transaction_attrib *attr = *head, *next;
+ *head = NULL;
+
+ do {
+ next = attr->next;
+ free((void *) attr->name);
+ free((void *) attr->value);
+ free(attr);
+ } while ((attr = next) != NULL);
+ }
+}
+
+struct transaction_attrib *transaction_attrib_from_tac_attrib(const struct tac_attrib *tac_attr)
+{
+ struct transaction_attrib *head = NULL, *tail;
+
+ for (; tac_attr != NULL; tac_attr = tac_attr->next) {
+ struct transaction_attrib *attr = transaction_attrib_new(tac_attr->attr);
+ if (! attr)
+ continue;
+
+ if (head) {
+ tail->next = attr;
+ tail = attr;
+ }
+ else {
+ head = tail = attr;
+ }
+ }
+
+ return head;
+}
+
+static int tacplus_add_attrib(struct tac_attrib **attr, char *name,
+ char *value, bool truncate)
+{
+ syslog(LOG_DEBUG, "Appending mandatory attribute %s: %s", name, value);
+
+ return truncate ? tac_add_attrib_truncate(attr, name, value) :
+ tac_add_attrib(attr, name, value);
+}
+
+static int tacplus_add_optional_attrib(struct tac_attrib **attr, char *name,
+ char *value, bool truncate)
+{
+ syslog(LOG_DEBUG, "Appending optional attribute %s: %s", name, value);
+
+ return truncate ? tac_add_attrib_pair_truncate(attr, name,
+ OPTIONAL_ATTR_SEP, value) :
+ tac_add_attrib_pair(attr, name, OPTIONAL_ATTR_SEP, value);
+}
+
+int tacplus_author_send(struct transaction *t)
+{
+ struct tac_attrib *attr = NULL;
+ char *addr_str;
+ struct tac_session_extra *extra;
+ struct areply author_rep = { .status = TAC_PLUS_AUTHOR_STATUS_ERROR };
+
+ assert(t->type == TRANSACTION_AUTHOR);
+ t->response.author.status = author_rep.status;
+
+ /* Attempt to populate cmd and args before connecting to server */
+ char **cmd_arg = t->request.author.cmd;
+ if (cmd_arg && *cmd_arg) {
+ if (tacplus_add_attrib(&attr, "cmd", *cmd_arg, false) < 0)
+ goto unable_to_send;
+
+ for (cmd_arg++; cmd_arg && *cmd_arg; cmd_arg++) {
+ if (tacplus_add_attrib(&attr, "cmd-arg", *cmd_arg, false) < 0)
+ goto unable_to_send;
+ }
+ }
+
+ if (tacplus_connect() == false) {
+ syslog(LOG_NOTICE, "Failed to connect to a TACACS+ server for "
+ "authorization transaction");
+ goto finish;
+ }
+
+ struct tac_session_extra _extra = {};
+ extra = tacplus_current_session_extra(connControl->opts, &_extra);
+
+ if (tacplus_add_attrib(&attr, "protocol",
+ t->request.author.protocol, false) < 0)
+ goto unable_to_send;
+
+ if (tacplus_add_attrib(&attr, "service",
+ t->request.author.service, false) < 0)
+ goto unable_to_send;
+
+ if (t->request.author.secrets &&
+ tacplus_add_optional_attrib(&attr, "secrets",
+ t->request.author.secrets, false) < 0)
+ goto unable_to_send;
+
+ addr_str = addrinfo_to_string(extra->server->addrs);
+
+ syslog(LOG_DEBUG, "Sending authorization request to %s",
+ strOrNil(addr_str));
+
+ free(addr_str);
+
+ t->response.author.status = tac_author_send(extra->server->fd, t->request.author.login,
+ t->request.author.tty, t->request.author.r_addr, attr);
+ if (t->response.author.status < 0)
+ {
+ syslog(LOG_NOTICE, "Error sending authorization request for user: %s <%d>\n",
+ t->request.author.login, t->response.author.status);
+ tacplus_server_activate_hold_down(extra->server);
+ goto finish;
+ } else {
+ inc_author_requests(extra->server_id);
+ }
+
+ t->response.author.status = tac_author_read_timeout(extra->server->fd, &author_rep, extra->server->timeout);
+
+ if (author_rep.status < 0) {
+ syslog(LOG_NOTICE, "Failed to read authorization response for user: %s <%d>\n",
+ t->request.author.login, author_rep.status);
+ tacplus_server_activate_hold_down(extra->server);
+ goto finish;
+ }
+
+ switch (author_rep.status) {
+ case TAC_PLUS_AUTHOR_STATUS_PASS_ADD:
+ syslog(LOG_DEBUG, "Authorization received: PASS_ADD");
+ break;
+
+ case TAC_PLUS_AUTHOR_STATUS_PASS_REPL:
+ syslog(LOG_DEBUG, "Authorization received: PASS_REPLACE");
+ break;
+
+ case TAC_PLUS_AUTHOR_STATUS_FAIL:
+ syslog(LOG_DEBUG, "Authorization received: FAILED");
+ break;
+
+ case TAC_PLUS_AUTHOR_STATUS_ERROR:
+ syslog(LOG_DEBUG, "Authorization received: ERROR");
+ break;
+
+ case TAC_PLUS_AUTHOR_STATUS_FOLLOW:
+ syslog(LOG_DEBUG, "Authorization received: FOLLOW");
+ break;
+
+ default:
+ syslog(LOG_DEBUG, "Authorization received: UNKNOWN");
+ break;
+ }
+
+ inc_author_replies(extra->server_id);
+ goto finish;
+
+unable_to_send:
+ syslog(LOG_NOTICE, "Unable to send authorization request");
+
+finish:
+ t->response.author.attrs = transaction_attrib_from_tac_attrib(author_rep.attr);
+
+ tacplus_close();
+ tac_free_attrib(&attr);
+ tac_free_attrib(&author_rep.attr);
+ free(author_rep.msg);
+ return t->response.author.status;
+}
+
+static int tacplus_acct_send_per_session(struct transaction *t)
+{
+ struct tac_attrib *attr;
+ struct tac_session_extra *extra;
+ char *addr_str;
+ struct areply re = {0};
+
+ struct tac_session_extra _extra = {};
+ extra = tacplus_current_session_extra (connControl->opts, &_extra);
+
+ attr = NULL;
+
+ if (t->request.account.task_id)
+ if (tacplus_add_attrib(&attr, "task_id", t->request.account.task_id, false) < 0)
+ goto unable_to_send;
+
+ if (t->request.account.start_time)
+ if (tacplus_add_attrib(&attr, "start_time", t->request.account.start_time, false) < 0)
+ goto unable_to_send;
+
+ if (t->request.account.stop_time)
+ if (tacplus_add_attrib(&attr, "stop_time", t->request.account.stop_time, false) < 0)
+ goto unable_to_send;
+
+ if (t->request.account.service)
+ if (tacplus_add_attrib(&attr, "service", t->request.account.service, false) < 0)
+ goto unable_to_send;
+
+ if (t->request.account.protocol)
+ if (tacplus_add_attrib(&attr, "protocol", t->request.account.protocol, false) < 0)
+ goto unable_to_send;
+
+ char **cmd_arg = t->request.account.command;
+ if (cmd_arg && *cmd_arg) {
+ if (tacplus_add_attrib(&attr, "cmd", *cmd_arg, true) < 0)
+ goto unable_to_send;
+
+ for (cmd_arg++; cmd_arg && *cmd_arg; cmd_arg++) {
+ if (tacplus_add_attrib(&attr, "cmd-arg", *cmd_arg, true) < 0)
+ goto unable_to_send;
+ }
+ }
+
+ addr_str = addrinfo_to_string(extra->server->addrs);
+
+ syslog(LOG_DEBUG, "Sending accounting request to %s",
+ strOrNil(addr_str));
+
+ free(addr_str);
+
+ if (tac_acct_send(extra->server->fd, t->request.account.account_flag, t->request.account.name,
+ t->request.account.tty, t->request.account.r_addr, attr) < 0) {
+ syslog(LOG_ERR, "Error sending accounting request");
+ tacplus_server_activate_hold_down(extra->server);
+ t->response.account.status = TAC_PLUS_ACCT_STATUS_ERROR;
+ goto finish;
+ } else {
+ inc_acct_requests(extra->server_id);
+ }
+
+ t->response.account.status = tac_acct_read_timeout(extra->server->fd, &re, extra->server->timeout);
+ if (t->response.account.status < 0) {
+ syslog(LOG_ERR, "Error reading accounting reply: %d", t->response.account.status);
+ tacplus_server_activate_hold_down(extra->server);
+ goto finish;
+ }
+
+ switch (t->response.account.status) {
+ case TAC_PLUS_ACCT_STATUS_SUCCESS:
+ syslog(LOG_DEBUG, "Accounting received: SUCCESS");
+ break;
+
+ case TAC_PLUS_ACCT_STATUS_ERROR:
+ syslog(LOG_DEBUG, "Accounting received: ERROR");
+ break;
+
+ case TAC_PLUS_ACCT_STATUS_FOLLOW:
+ syslog(LOG_DEBUG, "Accounting received: FOLLOW");
+ break;
+
+ default:
+ syslog(LOG_DEBUG, "Accounting received: UNKNOWN");
+ break;
+ }
+
+ inc_acct_replies(extra->server_id);
+ goto finish;
+
+unable_to_send:
+ syslog(LOG_NOTICE, "Unable to send accounting request");
+
+finish:
+ tac_free_attrib(&attr);
+ tac_free_attrib(&re.attr);
+ free(re.msg);
+ return t->response.account.status;
+}
+
+int tacplus_acct_send(struct transaction *t)
+{
+ assert(t->type == TRANSACTION_ACCOUNT);
+ t->response.account.status = TAC_PLUS_ACCT_STATUS_ERROR;
+
+ if (connControl->opts->broadcast) {
+ syslog(LOG_ERR, "Broadcast mode is not supported!");
+ goto finish;
+ }
+
+ if (tacplus_connect() == false) {
+ syslog(LOG_NOTICE, "Failed to connect to a TACACS+ server for "
+ "accounting transaction");
+ goto finish;
+ }
+
+ tacplus_acct_send_per_session(t);
+ tacplus_close();
+
+finish:
+ return t->response.account.status;
+}
+
+int tacplus_authen_send(struct transaction *t)
+{
+ char *addr_str;
+ struct tac_session_extra *extra;
+
+ assert(t->type == TRANSACTION_AUTHEN);
+ t->response.authen.status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+
+ if (tacplus_connect() == false) {
+ syslog(LOG_NOTICE, "Failed to connect to a TACACS+ server for "
+ "authentication transaction");
+ goto finish;
+ }
+
+ struct tac_session_extra _extra = {};
+ extra = tacplus_current_session_extra (connControl->opts, &_extra);
+
+ addr_str = addrinfo_to_string(extra->server->addrs);
+
+ syslog(LOG_DEBUG, "Sending authentication request to %s",
+ strOrNil(addr_str));
+
+ free(addr_str);
+
+ if (tac_authen_send(extra->server->fd, t->request.authen.user,
+ t->request.authen.password, t->request.authen.tty,
+ t->request.authen.r_addr) < 0)
+ {
+ syslog(LOG_ERR, "Error sending authentication request");
+ t->response.authen.status = TAC_PLUS_AUTHEN_STATUS_ERROR;
+ tacplus_server_activate_hold_down(extra->server);
+ goto finish;
+ }
+ else {
+ inc_authen_requests(extra->server_id);
+ }
+
+ bool auth_in_prog = true;
+
+ while (auth_in_prog) {
+ t->response.authen.status = tac_authen_read_timeout(extra->server->fd, extra->server->timeout);
+ if (t->response.authen.status < 0) {
+ syslog(LOG_ERR, "Error reading authentication reply: %d",
+ t->response.authen.status);
+ tacplus_server_activate_hold_down(extra->server);
+ goto finish;
+ }
+
+ switch (t->response.authen.status) {
+ case TAC_PLUS_AUTHEN_STATUS_PASS:
+ auth_in_prog = false;
+ syslog(LOG_DEBUG, "Authentication received: PASS");
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_FAIL:
+ auth_in_prog = false;
+ syslog(LOG_DEBUG, "Authentication received: FAIL");
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_GETDATA:
+ /* FALLTHRU */
+ case TAC_PLUS_AUTHEN_STATUS_GETUSER:
+ syslog(LOG_DEBUG, "Authentication received: CONTINUE");
+
+ /* FALLTHRU */
+ case TAC_PLUS_AUTHEN_STATUS_RESTART:
+ auth_in_prog = false;
+ syslog(LOG_DEBUG, "Authentication received: RESTART");
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_FOLLOW:
+ auth_in_prog = false;
+ syslog(LOG_DEBUG, "Authentication received: FOLLOW");
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_GETPASS:
+ if (tac_cont_send(extra->server->fd, t->request.authen.password) < 0) {
+ syslog(LOG_NOTICE, "Could not send TACACS+ password for user %s.\n", t->request.authen.user);
+ /* This means the dbus client will receive a return
+ * value of TAC_PLUS_AUTHEN_STATUS_GETPASS.
+ */
+ goto finish;
+ }
+ /* continue the loop and read the TACACS+ server response to the supplied password */
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_ERROR:
+ auth_in_prog = false;
+ syslog(LOG_DEBUG, "Authentication received: ERROR");
+ break;
+
+ default:
+ auth_in_prog = false;
+ break;
+ }
+ }
+
+ inc_authen_replies(extra->server_id);
+
+finish:
+ tacplus_close();
+ return t->response.authen.status;
+}
diff --git a/tacplus-daemon/transaction.h b/tacplus-daemon/transaction.h
new file mode 100644
index 0000000..99c0f4f
--- /dev/null
+++ b/tacplus-daemon/transaction.h
@@ -0,0 +1,101 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#ifndef _TRANSACTION_H
+#define _TRANSACTION_H
+
+
+struct authen_send_param {
+ char *user;
+ char *password;
+ char *tty;
+ char *r_addr;
+};
+
+struct author_send_param {
+ char *login;
+ char *tty;
+ char *r_addr;
+ char *protocol;
+ char *service;
+ char *secrets;
+ char **cmd;
+};
+
+struct account_send_param {
+ int account_flag;
+ char *name;
+ char *tty;
+ char *r_addr;
+ char *task_id;
+ char *start_time;
+ char *stop_time;
+ char *service;
+ char *protocol;
+ char **command;
+};
+
+struct account_send_response {
+ int status;
+};
+
+struct authen_send_response {
+ int status;
+};
+
+struct author_send_response {
+ int status;
+ struct transaction_attrib *attrs;
+};
+
+typedef enum {
+ TRANSACTION_AUTHEN = 1,
+ TRANSACTION_AUTHOR,
+ TRANSACTION_ACCOUNT,
+ TRANSACTION_INVALID,
+} transaction_type_t;
+
+const char *transaction_type_str(transaction_type_t);
+
+struct transaction {
+ transaction_type_t type;
+
+ union {
+ struct authen_send_param authen;
+ struct author_send_param author;
+ struct account_send_param account;
+ } request;
+
+ union {
+ struct authen_send_response authen;
+ struct author_send_response author;
+ struct account_send_response account;
+ } response;
+
+ void *user;
+};
+
+struct transaction *transaction_new(transaction_type_t);
+void transaction_free(struct transaction **);
+
+struct transaction_attrib {
+ struct transaction_attrib *next;
+ const char *name;
+ const char *value;
+};
+
+struct transaction_attrib *transaction_attrib_new(const char *);
+void transaction_attrib_free(struct transaction_attrib **);
+
+int tacplus_author_send(struct transaction *);
+
+int tacplus_acct_send(struct transaction *);
+
+int tacplus_authen_send(struct transaction *);
+
+#endif /*_TRANSACTION_H */
diff --git a/tacplus-daemon/transaction_private.h b/tacplus-daemon/transaction_private.h
new file mode 100644
index 0000000..2f8a799
--- /dev/null
+++ b/tacplus-daemon/transaction_private.h
@@ -0,0 +1,19 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2019 AT&T Intellectual Property.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#ifndef _TRANSACTION_PRIVATE_H
+#define _TRANSACTION_PRIVATE_H
+
+/*
+ * This header leaks the TACACS+ implementation being used (ie. libtac)
+ * and is only intended to be included by transaction.c and appropriate UTs.
+ */
+
+struct transaction_attrib *transaction_attrib_from_tac_attrib(const struct tac_attrib *);
+
+#endif /*_TRANSACTION_PRIVATE_H */
diff --git a/tacplus-daemon/utils.c b/tacplus-daemon/utils.c
new file mode 100644
index 0000000..442d259
--- /dev/null
+++ b/tacplus-daemon/utils.c
@@ -0,0 +1,330 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#include <syslog.h>
+#include <stdlib.h>
+#include <math.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <utmpx.h>
+
+#include "utils.h"
+
+bool sockaddr_addr_equal (const struct sockaddr *sa1, const struct sockaddr *sa2)
+{
+ if (sa1->sa_family != sa2->sa_family)
+ return false;
+
+ if (sa1->sa_family == AF_INET) {
+ struct in_addr *sin1 = &((struct sockaddr_in *)sa1)->sin_addr;
+ struct in_addr *sin2 = &((struct sockaddr_in *)sa2)->sin_addr;
+
+ return sin1->s_addr == sin2->s_addr ? true : false;
+ }
+ else if (sa1->sa_family == AF_INET6) {
+ struct in6_addr *sin1 = &((struct sockaddr_in6 *)sa1)->sin6_addr;
+ struct in6_addr *sin2 = &((struct sockaddr_in6 *)sa2)->sin6_addr;
+
+ return memcmp(sin1, sin2, sizeof *sin1) == 0 ? true : false;
+ }
+
+ return false;
+}
+
+struct addrinfo *tacplus_addrinfo(const char *opt_server, const char *opt_port) {
+ struct addrinfo *result = NULL;
+ static const struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM
+ };
+
+ int err = getaddrinfo(opt_server, opt_port, &hints, &result);
+
+ if (err != 0) {
+ syslog(LOG_ERR, "resolving %s:%s error: %s",
+ strOrNil(opt_server), strOrNil(opt_port),
+ gai_strerror(err));
+ /* TODO: error handling */
+ return NULL;
+ }
+
+ return result;
+}
+
+char *addrinfo_to_string(const struct addrinfo *addr)
+{
+ char addr_str[INET6_ADDRSTRLEN];
+
+ if (getnameinfo(addr->ai_addr, addr->ai_addrlen, addr_str,
+ INET6_ADDRSTRLEN, 0, 0, NI_NUMERICHOST) == 0)
+ return strdup(addr_str);
+ else
+ syslog(LOG_DEBUG, "Could not convert address to string");
+
+ return NULL;
+}
+
+uint16_t get_addrinfo_port(const struct addrinfo *ai)
+{
+ struct sockaddr *sa = ai->ai_addr;
+
+ if (sa->sa_family == AF_INET)
+ return ntohs(((struct sockaddr_in *)sa)->sin_port);
+ else if (sa->sa_family == AF_INET6)
+ return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
+ else
+ return 0;
+}
+
+int is_sockaddr_loopback(struct sockaddr *saddr)
+{
+ switch (saddr->sa_family) {
+ case AF_INET:
+ return IS_INADDR_LOOPBACK(((struct sockaddr_in *)saddr)->sin_addr);
+ case AF_INET6:
+ return IS_IN6ADDR_LOOPBACK(((struct sockaddr_in6 *)saddr)->sin6_addr);
+ }
+
+ return 0;
+}
+
+struct addrinfo *get_interface_addrinfo(const char *ifname, int af)
+{
+ struct ifaddrs *ifas_head = NULL, *ifa;
+ struct addrinfo *info = NULL;
+ socklen_t addrlen;
+ int ret;
+
+ if ((ret = getifaddrs(&ifas_head))) {
+ syslog(LOG_WARNING, "getifaddrs() failed (%i): %s",
+ ret, strerror(errno));
+ return NULL;
+ }
+
+ for (ifa = ifas_head; ifa; ifa = ifa->ifa_next) {
+ if (! ifa->ifa_addr)
+ continue;
+
+ if (ifa->ifa_addr->sa_family != af)
+ continue;
+
+ if (strncmp(ifname, ifa->ifa_name, IFNAMSIZ))
+ continue;
+
+ if (is_sockaddr_loopback(ifa->ifa_addr))
+ continue;
+
+ if (! (info = (struct addrinfo *) calloc(1, sizeof(struct addrinfo)))) {
+ syslog(LOG_ERR, "get_interface_addrinfo(): addrinfo "
+ "memory allocation failure");
+ goto finish;
+ }
+
+ addrlen = af == AF_INET ? sizeof(struct sockaddr_in)
+ : sizeof(struct sockaddr_in6);
+
+ if (! (info->ai_addr = (struct sockaddr *) malloc(addrlen))) {
+ syslog(LOG_ERR, "get_interface_addrinfo(): sockaddr "
+ "memory allocation failure");
+ free(info);
+ goto finish;
+ }
+
+ memcpy(info->ai_addr, ifa->ifa_addr, addrlen);
+ info->ai_family = ifa->ifa_addr->sa_family;
+ info->ai_addrlen = addrlen;
+ break;
+ }
+
+ if (! info)
+ syslog(LOG_DEBUG, "Interface %s does not exist or has no "
+ "suitable addresses", ifname);
+
+finish:
+ freeifaddrs(ifas_head);
+ return info;
+}
+
+void free_interface_addrinfo(struct addrinfo **info)
+{
+ if (! info || !*info)
+ return;
+
+ free((*info)->ai_addr);
+ free(*info);
+ *info = NULL;
+}
+
+int is_interface_up(const char *ifname)
+{
+ struct ifreq req = {};
+ int fd;
+
+ fd = socket(AF_UNIX, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ syslog(LOG_ERR, "is_interface_up(%s): failed to open socket (%u): %s",
+ ifname, errno, strerror(errno));
+ return -1;
+ }
+
+ strncat(req.ifr_name, ifname, sizeof(req.ifr_name)-1);
+ if (ioctl(fd, SIOCGIFFLAGS, &req) < 0) {
+ syslog(LOG_WARNING, "is_interface_up(%s): could not get interface "
+ "status (%u): %s", req.ifr_name, errno, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ return (req.ifr_flags & IFF_UP) ? 1 : 0;
+}
+
+void
+cur_mono_time(struct timespec *ts)
+{
+ if (clock_gettime(CLOCK_MONOTONIC_RAW, ts) < 0) {
+ syslog(LOG_ERR, "Error getting current time: %s", strerror(errno));
+ SET_TIMESPEC_VALS(*ts, 0, 0);
+ }
+
+ timespec_normalise(ts);
+}
+
+/*
+ * Adjust the timespec spec to ensure that the nanosecond portion is in the
+ * range 0-999,999,999.
+ */
+struct timespec *
+timespec_normalise(struct timespec *spec)
+{
+ while (spec->tv_nsec <= -SEC_TO_NSECS) {
+ spec->tv_sec--;
+ spec->tv_nsec += SEC_TO_NSECS;
+ }
+
+ while (spec->tv_nsec < 0) {
+ spec->tv_sec--;
+ spec->tv_nsec = SEC_TO_NSECS + spec->tv_nsec;
+ }
+
+ while (spec->tv_nsec >= SEC_TO_NSECS) {
+ spec->tv_sec++;
+ spec->tv_nsec -= SEC_TO_NSECS;
+ }
+
+ return spec;
+}
+
+/*
+ * Subtract time b from a, placing the result in result
+ */
+struct timespec *
+timespec_sub(const struct timespec *a, const struct timespec *b,
+ struct timespec *result)
+{
+ result->tv_sec = a->tv_sec - b->tv_sec;
+ result->tv_nsec = a->tv_nsec - b->tv_nsec;
+ return timespec_normalise(result);
+}
+
+/*
+ * Return -1, 0, or 1 if a represents a time less than, equal to, or
+ * greater than the time represented by b, respectively.
+ */
+int
+timespec_cmp(const struct timespec *a, const struct timespec *b)
+{
+ if (a->tv_sec > b->tv_sec)
+ return 1;
+ else if (a->tv_sec < b->tv_sec)
+ return -1;
+
+ if (a->tv_nsec > b->tv_nsec)
+ return 1;
+ else if (a->tv_nsec < b->tv_nsec)
+ return -1;
+
+ /* timespecs equal */
+ return 0;
+}
+
+/*
+ * Get the user's remote login address for a given TTY
+ *
+ * WARNING: this function is not thread safe due to the following calls:
+ * - setutxent()
+ * - getutxline()
+ * - endutxent()
+ */
+char *
+get_tty_login_addr(const char *tty)
+{
+ struct utmpx tty_utmp = {0};
+
+ if (!tty) {
+ return NULL;
+ }
+
+ strncpy(tty_utmp.ut_line, tty, sizeof tty_utmp.ut_line);
+
+ setutxent();
+ struct utmpx *up = getutxline(&tty_utmp);
+ endutxent();
+ if (!up || strlen(up->ut_host) == 0)
+ return NULL;
+
+ return strdup(up->ut_host);
+}
+
+int
+new_cb_timer(timer_t *timer, void (*cb) (union sigval), union sigval *user)
+{
+ struct sigevent se = {
+ .sigev_notify = SIGEV_THREAD,
+ .sigev_notify_function = cb
+ };
+
+ if (user)
+ se.sigev_value = *user;
+
+ int ret = timer_create(CLOCK_MONOTONIC, &se, timer);
+ if (ret < 0)
+ syslog(LOG_ERR, "timer_create() failed (%d): %s", ret, strerror(errno));
+
+ return ret;
+}
+
+int
+set_timer(timer_t timer, const struct itimerspec *it)
+{
+ struct itimerspec discard;
+
+ int ret = timer_settime(timer, 0, it, &discard);
+ if (ret < 0)
+ syslog(LOG_ERR, "timer_settime() failed (%d): %s", ret, strerror(errno));
+
+ return ret;
+}
+
+int
+expire_timer(timer_t timer)
+{
+ struct itimerspec it = {
+ .it_value.tv_sec = 0,
+ .it_value.tv_nsec = 1
+ };
+
+ return set_timer(timer, &it);
+}
diff --git a/tacplus-daemon/utils.h b/tacplus-daemon/utils.h
new file mode 100644
index 0000000..66e39ab
--- /dev/null
+++ b/tacplus-daemon/utils.h
@@ -0,0 +1,62 @@
+/*
+ TACACS+ D-Bus Daemon code
+
+ Copyright (c) 2018-2019 AT&T Intellectual Property.
+ Copyright (c) 2015-2016 Brocade Communications Systems, Inc.
+
+ SPDX-License-Identifier: GPL-2.0-only
+*/
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stdbool.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <time.h>
+
+#define IS_INADDR_LOOPBACK(I) ((ntohl(I.s_addr) & 0x7f000000) == 0x7f000000)
+#define IS_IN6ADDR_LOOPBACK(I) (memcmp(&I, &in6addr_loopback, sizeof(I)) == 0)
+
+#define SEC_TO_NSECS 1000000000
+
+#define TIMESPEC_VALS_EQ(T,S,N) ((T).tv_sec == S && (T).tv_nsec == N)
+
+#define SET_TIMESPEC_VALS(T,S,N) \
+ { \
+ (T).tv_sec = S; \
+ (T).tv_nsec = N; \
+ }
+
+struct addrinfo *tacplus_addrinfo(const char *, const char *);
+
+char *addrinfo_to_string(const struct addrinfo *);
+
+uint16_t get_addrinfo_port(const struct addrinfo *);
+
+static inline
+const char *strOrNil(const char *s)
+{
+ return (s ? s : "(nil)");
+}
+
+bool sockaddr_addr_equal (const struct sockaddr *sa1, const struct sockaddr *sa2);
+struct addrinfo *get_interface_addrinfo(const char *ifname, int af);
+void free_interface_addrinfo(struct addrinfo **info);
+int is_sockaddr_loopback(struct sockaddr *saddr);
+int is_interface_up(const char *ifname);
+void cur_mono_time(struct timespec *ts);
+struct timespec *timespec_normalise(struct timespec *spec);
+struct timespec *timespec_sub(const struct timespec *a,
+ const struct timespec *b,
+ struct timespec *result);
+int timespec_cmp(const struct timespec *a, const struct timespec *b);
+char *get_tty_login_addr(const char *tty);
+int new_cb_timer(timer_t *timer, void (*cb) (union sigval), union sigval *user);
+int set_timer(timer_t timer, const struct itimerspec *it);
+int expire_timer(timer_t timer);
+
+#endif /* UTILS_H */