diff options
author | Daniel Gollub <dgollub@att.com> | 2019-11-11 15:07:38 +0100 |
---|---|---|
committer | Vyatta Package Maintainers <DL-vyatta-help@att.com> | 2019-11-13 17:08:02 +0000 |
commit | 822c8f60b72cca97fb2c86db37835a60917d1c7e (patch) | |
tree | d5593a4ed1cbb8e9709411679974355a41f0289e | |
download | tacplusd-danos/1908.tar.gz tacplusd-danos/1908.zip |
DANOS ImportHEADdebian/1.19danos/1908master
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 @@ -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 @@ -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 */ |