summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2021-05-02 19:07:04 +0200
committerChristian Poessinger <christian@poessinger.com>2021-05-02 19:18:42 +0200
commitceb7d3cb30a23b4b148bc71491b3817e9e6e2778 (patch)
tree160ebe6294acb6a790790098b3861e58c0ca9ae4
downloadlibpam-tacplus-ceb7d3cb30a23b4b148bc71491b3817e9e6e2778.tar.gz
libpam-tacplus-ceb7d3cb30a23b4b148bc71491b3817e9e6e2778.zip
Initial import of libpam-tacplus (1.4.3-cl3u4)
-rw-r--r--AUTHORS5
-rw-r--r--COPYING281
-rw-r--r--ChangeLog259
-rw-r--r--INSTALL370
-rw-r--r--Makefile.am94
-rw-r--r--NEWS1
-rw-r--r--Pam.d.common-example53
-rw-r--r--README.md191
-rwxr-xr-xauto.sh4
-rw-r--r--configure.ac114
-rw-r--r--debian/README.Debian18
-rw-r--r--debian/README.source5
-rw-r--r--debian/changelog114
-rw-r--r--debian/compat1
-rw-r--r--debian/control47
-rw-r--r--debian/copyright30
-rw-r--r--debian/docs2
-rw-r--r--debian/libpam-tacplus-dev.install1
-rw-r--r--debian/libpam-tacplus.install4
-rw-r--r--debian/libpam-tacplus.postinst7
-rw-r--r--debian/libpam-tacplus.prerm9
-rw-r--r--debian/libtac-dev.install3
-rw-r--r--debian/libtac2-bin.install2
-rw-r--r--debian/libtac2.install1
-rw-r--r--debian/libtac2.symbols33
-rwxr-xr-xdebian/rules30
-rw-r--r--debian/source/format2
-rw-r--r--debian/tacplus15
-rw-r--r--debian/watch3
-rw-r--r--libtac.pc.in11
-rw-r--r--libtac/include/cdefs.h62
-rw-r--r--libtac/include/libtac.h173
-rw-r--r--libtac/include/tacplus.h231
-rw-r--r--libtac/lib/acct_r.c190
-rw-r--r--libtac/lib/acct_s.c179
-rw-r--r--libtac/lib/attrib.c99
-rw-r--r--libtac/lib/authen_r.c197
-rw-r--r--libtac/lib/authen_s.c190
-rw-r--r--libtac/lib/author_r.c283
-rw-r--r--libtac/lib/author_s.c154
-rw-r--r--libtac/lib/connect.c236
-rw-r--r--libtac/lib/cont_s.c104
-rw-r--r--libtac/lib/crypt.c114
-rw-r--r--libtac/lib/hdr_check.c50
-rw-r--r--libtac/lib/header.c94
-rw-r--r--libtac/lib/magic.c88
-rw-r--r--libtac/lib/magic.h30
-rw-r--r--libtac/lib/md5.c273
-rw-r--r--libtac/lib/md5.h45
-rw-r--r--libtac/lib/messages.c31
-rw-r--r--libtac/lib/messages.h36
-rw-r--r--libtac/lib/read_wait.c136
-rw-r--r--libtac/lib/version.c24
-rw-r--r--libtac/lib/xalloc.c78
-rw-r--r--libtac/lib/xalloc.h33
-rw-r--r--pam_tacplus.c1104
-rw-r--r--pam_tacplus.h50
-rw-r--r--pam_tacplus.spec.in90
-rw-r--r--sample.pam21
-rw-r--r--support.c649
-rw-r--r--support.h62
-rw-r--r--tacc.1146
-rw-r--r--tacc.c601
-rw-r--r--tacplus_servers46
-rw-r--r--tacplus_servers.5139
65 files changed, 7748 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..05878f7
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,5 @@
+Primary Authors:
+ Pawel Krawczyk <pawel.krawczyk@hush.com>
+ Jeroen Nijhof <jeroen@jeroennijhof.nl>
+All Contributors:
+ https://github.com/pwdng/pam_tacplus/graphs/contributors
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..fb06a42
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,281 @@
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, 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 Library 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
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..427d6b5
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,259 @@
+1.4.0-1
+Dave Olson, June 2016
+
+Changes to support local mapping, so that TACACS users do not need
+entries in /etc/passwd to supply home directory, uid, and gid information.
+
+This was done by using a new mapping library libtacplus_map. See that
+package for details.
+
+Also see the comments about immutable loginuid in Pam.d.common-example
+
+libtac is converted to a shared library, so it can be used by other programs,
+and only functions and variables starting with tac_* are exported in the
+shared library. Some functions were renamed to make this possible.
+
+A separate package libnss_tacplus uses the mapping library to do lookups by
+both name and uid. uid lookups are only possible while a tacacs user is
+logged in.
+
+If multiple tacacs users at the same privilege level are logged in, the
+current behavior is that is that if a call is done from within the login
+session, the correct (login) name will be returned. If from outside the
+session (audit uid and/or session don't match in the mapping file), the name
+from first map entry is used, much like normal systems where multiple users
+have the same UID.
+
+Added the runtime config capability to include another file, so that
+the tacacs servers are only listed in a single place. Ship using
+/etc/tacplus_servers as an include file, and use it in the pam sample config
+Because that's common, allow debug=NUMBER for pam_tacplus, as well as plain
+"debug".
+
+Renamed external libtac functions to all have a tac_ prefix, to avoid
+name collision with other programs (the x*alloc family was an issue, in
+particular). This is an API change, but since library just got bumped from
+1.0 to 2.0, left it at 2.0
+
+Enabled -Werror to catch errors early (and fixed a few related items).
+
+1.4.0
+* Use openssl by default for crypto
+
+1.3.9
+* Close file descriptor leak
+* Add client_connect_source_address
+
+1.3.8
+* A lot of cleanups and improvements by Walter de Jong <walter@heiho.net>
+* Fixed build instruction in spec file and INSTALL
+* Active_server can not be a pointer, data lost after authentication.
+* Added port option per server, thanks to Luc Ducazu <lducazu@gmail.com>
+* Fixed missing FIONREAD for solaris
+* Rearranged header file include for libtac.h, fixes AIX compile problems
+* Renamed rem_addr, rem_addr_len to r_addr and r_addr_len
+
+1.3.7
+* Tac_encryption fully handled by libtac no need to enable it manually
+* Fixed connection handling in _pam_account,
+ thanks to James Allwright <jamesallwright@yahoo.co.uk>
+* Handle attributes which contains no value,
+ thanks to James Allwright <jamesallwright@yahoo.co.uk>
+* Global variables tac_login and tac_secret not static anymore,
+ pointed out by James Allwright <jamesallwright@yahoo.co.uk>
+* version.c: libtac version 1.8.1
+* pam_tacplus.c: moved debug message after active_server validation, avoiding
+ null pointer exception
+* attrib.c: explicity setting *attr to NULL after free(),
+ thanks to Anthony Low <anthonyl@xkl.com>
+
+1.3.6
+* Added libpam-runtime support for debian
+* Added use_first_pass and try_first_pass option, thanks to Luc Ducazu <lducazu@gmail.com>
+* Changed e-mail adres to jeroen@jeroennijhof.nl
+* Improved accounting, added cmd attribute for command logging
+* Added tac_acct_flag2str()
+* Renamed tac_account_read, tac_account_send to tac_acct_read and tac_acct_send
+* pam_tacplus.spec.in: fixed static library path and pam_tacplus.so location
+* Debian packaging improvements
+
+1.3.5
+* This version will be dedicated to Darren Besler, thank you for your major
+ contribution!
+
+* libtac version is now 1.7.1
+* magic.c: magic_inited is only used for linux
+* Finally got rid of all goto illness!
+* Changed tabsize to 4
+* Fixed missing xalloc.h in authen_s.c
+* Get PAM_RHOST from PAM stack and use it as rem_addr
+* Added _pam_get_rhost() and _pam_get_user()
+
+* The following is done by Darren Besler:
+- add ability to set more elements of tacacs+ packet from parameters or globals
+- cleanup messaging to be consistent with function and presentation format
+- cleanup how strings are handled and returned
+- acct and author read require areply.msg to be freed by caller now
+- cast return values
+- added port # to formatted IP address
+- add timeout on read capability
+- cleanup method messages are returned to caller, including adding a 0 byte
+ 0 byte added for safety reasons
+- caller must free areply.msg now.
+- add rem_addr as an argument
+- include rem_addr in packet
+- include ability to set priv_lvl in packet
+- add ability to set authen_service from global variable aot fixed value
+
+Bugs fixed by Darren Besler:
+- cleanup various memory leaks, lost memory, and dangling pointers
+- attrib.c: wasn't preserving '*' separator in attrib.c
+- author_r.c:
+- free attributes for replace status. Was always adding.
+- uncasted char* for length was producing negative length to bcopy for arg len > 127
+- possible null dereference when no separator
+- cont_s.c
+- was creating a new session id, should be using session id from authen start.
+- magic.c
+- magic was returning 0 on first call. Wasn't being initialized properly.
+
+Other changes by Darren Besler:
+* libtac/include/cdefs.h
+- add #ifndef guards
+
+* libtac/include/libtac.h
+- rename #ifndef guard to match filename
+- add extern "C" for C++
+- alter define for TACDEBUG
+- add define for TACSYSLOG
+- alter macro for TACDEBUG to be able to be used at runtime via tac_debug_enable
+- add declarations from tacplus.h not related to protocol
+- add defines for return status codes for library functions
+- add declarations for new additional global variables
+tac_priv_lvl
+tac_authen_method
+tac_authen_service
+tac_debug_enable
+tac_readtimeout_enable
+- revise declarations for functions to that have altered parameters lists, or return value
+
+* libtac/include/tacplus.h
+- move library specific declarations to libtac.h, leaving declarations
+here to be used for protocol specific details
+- add additional declarations for more complete coverage of tacacs+ protocol (v1.78)
+
+1.3.4
+* removed encrypt option just check if there is a secret (key).
+* removed first_hit option because you can get the same behaviour by using only one server.
+* added multiple secret support,
+ you can now specify different secrets (keys) for different servers.
+* connect.c: improved connection error handling by using getpeername() to check if connection
+ is still valid. This was needed since we are using non-blocking sockets.
+* properly handle multiple servers when authenticating, patch from Gregg Nemas, thanks!
+
+1.3.3
+* pam_tacplus.h: changed bitflags to hex, thanks Jason!
+* Added gitignore for build stuff
+* connect.c: removed ifdef for sys/socket.h, it will be included anyway for other platforms,
+ thanks to Obata Akio for pointing that out.
+* connect.c: improved connection error handling, patch from Martin Volf, thanks!
+
+1.3.2
+* Added autotool configuration files, thanks to Benoit Donneaux <benoit.donneaux@gmail.com>.
+* Added pam_tacplus.spec file, thanks to Benoit Donneaux <benoit.donneaux@gmail.com>.
+* Added license information to all files and the license itself.
+* All AV pairs are now available to the PAM environment. So you can use pam_exec.so or whatever
+ to do something with these. Only available for PAM account.
+* Rewritten attribute loop in function pam_sm_acct_mgmt() for debug and future use
+ of AV pairs.
+* Fixed attribute buffer in author_r.c, this bug cause program stuck when you get
+ AV pairs from the server, reported by Oz Shitrit.
+
+1.3.1
+* Added custom password prompt option
+* Removed password logging when in debug mode
+
+1.3.0
+* Released version 1.3.0 based on 1.2.13.
+ This release finally includes support for TACACS+ chap and login authentication. The
+ default is still pap for backward compatibility.
+
+1.2.13
+* Changed spaces into tabs for pam_tacplus.c so make it more readable
+* Did some minor cleanup
+* Added login option so you can choose which TACACS+ authentication you want to
+ use. You can use pap, chap or login (ascii) at the moment. The default login option is pap.
+* Added cont_s.c needed for TACACS+ login authentication.
+
+1.2.12
+* Missing network byte order convertion to host byte order in function's
+ tac_account_read, tac_authen_pap_read and tac_author_read, reported and
+ patch by Sven van den Steene, thanks!
+* Fixed potential memory leak, when tac_account_read and tac_authen_pap_read are
+ successful msg isn't freed, reported by Sven van den Steene
+
+1.2.11
+* Added NO_STATIC_MODULES to CFLAGS for linking with openpam on netbsd, tested by
+ Fredrik Pettai <pettai@nordu.net>
+* Removed libdl for compiling causing failure on netbsd, reported by
+ Fredrik Pettai <pettai@nordu.net>
+* hdr_check.c: forgot to include stdlib, reported by
+ Fredrik Pettai <pettai@nordu.net>
+* Changed defines to add support for netbsd, fixed by
+ Jeroen Nijhof <jeroen@nijhofnet.nl>
+* magic.c: read() can have a return value, fixed by
+ Jeroen Nijhof <jeroen@nijhofnet.nl>
+* support.c: _pam_log() va_list converted to string with vsnprintf() to support
+ syslog(), we have human readable error's in syslog again, fixed by
+ Jeroen Nijhof <jeroen@nijhofnet.nl>
+
+1.2.10
+ The following changes where made by Jeroen Nijhof <jeroen@nijhofnet.nl>
+* Changed default compile flags to be more compatible
+* Fixed serveral bugs including casts and cleanup's, the code can now compile
+ without any warnings
+* Changed some Makefile definitions to be more compatible with other versions of make
+* Support added for solaris and aix, tested on aix 5.3, solaris 9 and 10. Including
+ standalone version of cdefs.h
+
+1.2.9
+* Fixed bug with passing username and password, reported by
+ Mark Volpe <volpe.mark@epamail.epa.gov>
+* Fixed bug in passing the remote address, reported by
+ Jason Lambert <jlambert@lambert-comm.net> and
+ Yury Trembach <yt@sns.net.ua>
+* Fixed bug in reception of authorization packet, reported by
+ <svg@disney.surnet.ru>
+
+1.2.8
+* Another bugfix in tty handling - some daemons don't use any terminal, in
+ which case we send "unknown" terminal name to the TACACS+ server
+
+1.2.7
+* Fixed bug in tty determination
+
+1.2.6
+* Better protection against disconnection signals
+
+1.2.5
+* Fixed bug in task_id initialisation
+
+1.2.4
+* Fixed small bug in accounting
+
+1.2.3
+* upgraded to new libtac version, now pam_tacplus returns the attributes
+ received from server (currently only 'addr' attribute in PAM_RHOST)
+* minor fixes
+
+1.2.2
+* more fixes
+
+1.2.1
+* pam_sm_acct_mgmt() added
+* pam_sm_open_session() added
+* pam_sm_close_session() added
+* minor fixes
+
+1.0.1
+* first working version with pam_sm_authenticate()
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..2099840
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,370 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation,
+Inc.
+
+ Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved. This file is offered as-is,
+without warranty of any kind.
+
+Basic Installation
+==================
+
+ Briefly, the shell command `./configure && make && make install'
+should configure, build, and install this package. The following
+more-detailed instructions are generic; see the `README' file for
+instructions specific to this package. Some packages provide this
+`INSTALL' file but do not implement all of the features documented
+below. The lack of an optional feature in a given package is not
+necessarily a bug. More recommendations for GNU packages can be found
+in *note Makefile Conventions: (standards)Makefile Conventions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+ It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring. Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+ The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'. You need `configure.ac' if
+you want to change it or regenerate `configure' using a newer version
+of `autoconf'.
+
+ The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system.
+
+ Running `configure' might take a while. While running, it prints
+ some messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package, generally using the just-built uninstalled binaries.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation. When installing into a prefix owned by root, it is
+ recommended that the package be configured and built as a regular
+ user, and only the `make install' phase executed with root
+ privileges.
+
+ 5. Optionally, type `make installcheck' to repeat any self-tests, but
+ this time using the binaries in their final installed location.
+ This target does not install anything. Running this target as a
+ regular user, particularly if the prior `make install' required
+ root privileges, verifies that the installation completed
+ correctly.
+
+ 6. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+ 7. Often, you can also type `make uninstall' to remove the installed
+ files again. In practice, not all packages have tested that
+ uninstallation works correctly, even though it is required by the
+ GNU Coding Standards.
+
+ 8. Some packages, particularly those that use Automake, provide `make
+ distcheck', which can by used by developers to test that all other
+ targets like `make install' and `make uninstall' work correctly.
+ This target is generally not run by end users.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. Run `./configure --help'
+for details on some of the pertinent environment variables.
+
+ You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment. Here
+is an example:
+
+ ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+ *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you can use GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'. This
+is known as a "VPATH" build.
+
+ With a non-GNU `make', it is safer to compile the package for one
+architecture at a time in the source code directory. After you have
+installed the package for one architecture, use `make distclean' before
+reconfiguring for another architecture.
+
+ On MacOS X 10.5 and later systems, you can create libraries and
+executables that work on multiple system types--known as "fat" or
+"universal" binaries--by specifying multiple `-arch' options to the
+compiler but only a single `-arch' option to the preprocessor. Like
+this:
+
+ ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+ CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+ CPP="gcc -E" CXXCPP="g++ -E"
+
+ This is not guaranteed to produce working output in all cases, you
+may have to build one architecture at a time and combine the results
+using the `lipo' tool if you have problems.
+
+Installation Names
+==================
+
+ By default, `make install' installs the package's commands under
+`/usr/local/bin', include files under `/usr/local/include', etc. You
+can specify an installation prefix other than `/usr/local' by giving
+`configure' the option `--prefix=PREFIX', where PREFIX must be an
+absolute file name.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+pass the option `--exec-prefix=PREFIX' to `configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them. In general, the
+default for these options is expressed in terms of `${prefix}', so that
+specifying just `--prefix' will affect all of the other directory
+specifications that were not explicitly provided.
+
+ The most portable way to affect installation locations is to pass the
+correct locations to `configure'; however, many packages provide one or
+both of the following shortcuts of passing variable assignments to the
+`make install' command line to change installation locations without
+having to reconfigure or recompile.
+
+ The first method involves providing an override variable for each
+affected directory. For example, `make install
+prefix=/alternate/directory' will choose an alternate location for all
+directory configuration variables that were expressed in terms of
+`${prefix}'. Any directories that were specified during `configure',
+but not in terms of `${prefix}', must each be overridden at install
+time for the entire installation to be relocated. The approach of
+makefile variable overrides for each directory variable is required by
+the GNU Coding Standards, and ideally causes no recompilation.
+However, some platforms have known limitations with the semantics of
+shared libraries that end up requiring recompilation when using this
+method, particularly noticeable in packages that use GNU Libtool.
+
+ The second method involves providing the `DESTDIR' variable. For
+example, `make install DESTDIR=/alternate/directory' will prepend
+`/alternate/directory' before all installation names. The approach of
+`DESTDIR' overrides is not required by the GNU Coding Standards, and
+does not work on platforms that have drive letters. On the other hand,
+it does better at avoiding recompilation issues, and works well even
+when some directory options were not specified in terms of `${prefix}'
+at `configure' time.
+
+Optional Features
+=================
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+ Some packages offer the ability to configure how verbose the
+execution of `make' will be. For these packages, running `./configure
+--enable-silent-rules' sets the default to minimal output, which can be
+overridden with `make V=1'; while running `./configure
+--disable-silent-rules' sets the default to verbose, which can be
+overridden with `make V=0'.
+
+Particular systems
+==================
+
+ On HP-UX, the default C compiler is not ANSI C compatible. If GNU
+CC is not installed, it is recommended to use the following options in
+order to use an ANSI C compiler:
+
+ ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
+
+and if that doesn't work, install pre-built binaries of GCC for HP-UX.
+
+ HP-UX `make' updates targets which have the same time stamps as
+their prerequisites, which makes it generally unusable when shipped
+generated files such as `configure' are involved. Use GNU `make'
+instead.
+
+ On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
+parse its `<wchar.h>' header file. The option `-nodtk' can be used as
+a workaround. If GNU CC is not installed, it is therefore recommended
+to try
+
+ ./configure CC="cc"
+
+and if that doesn't work, try
+
+ ./configure CC="cc -nodtk"
+
+ On Solaris, don't put `/usr/ucb' early in your `PATH'. This
+directory contains several dysfunctional programs; working variants of
+these programs are available in `/usr/bin'. So, if you need `/usr/ucb'
+in your `PATH', put it _after_ `/usr/bin'.
+
+ On Haiku, software installed for all users goes in `/boot/common',
+not `/usr/local'. It is recommended to use the following options:
+
+ ./configure --prefix=/boot/common
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on. Usually, assuming the package is built to be run on the
+_same_ architectures, `configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+`--build=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+ CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+ OS
+ KERNEL-OS
+
+ See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+ If you are _building_ compiler tools for cross-compiling, you should
+use the option `--target=TYPE' to select the type of system they will
+produce code for.
+
+ If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+ Variables not defined in a site shell script can be set in the
+environment passed to `configure'. However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost. In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'. For example:
+
+ ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for `CONFIG_SHELL' due to
+an Autoconf limitation. Until the limitation is lifted, you can use
+this workaround:
+
+ CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+`configure' Invocation
+======================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--help'
+`-h'
+ Print a summary of all of the options to `configure', and exit.
+
+`--help=short'
+`--help=recursive'
+ Print a summary of the options unique to this package's
+ `configure', and exit. The `short' variant lists options used
+ only in the top level, while the `recursive' variant lists options
+ also present in any nested packages.
+
+`--version'
+`-V'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--cache-file=FILE'
+ Enable the cache: use and save the results of the tests in FILE,
+ traditionally `config.cache'. FILE defaults to `/dev/null' to
+ disable caching.
+
+`--config-cache'
+`-C'
+ Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--prefix=DIR'
+ Use DIR as the installation prefix. *note Installation Names::
+ for more details, including other options available for fine-tuning
+ the installation locations.
+
+`--no-create'
+`-n'
+ Run the configure checks, but stop before creating any output
+ files.
+
+`configure' also accepts some other, not widely useful, options. Run
+`configure --help' for more details.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..1d706bb
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,94 @@
+###########################################################################
+## File: ./Makefile.am
+## Versions: $Id: Makefile.am,v 1.3 2010/06/11 12:04:29 j-nijhof Exp $
+## Created: 2010/06/09
+## Modified 2016
+## Copyright 2015, 2016 Cumulus Networks, Inc. All rights reserved.
+##
+###########################################################################
+
+ACLOCAL_AMFLAGS = -I config
+AUTOMAKE_OPTIONS = subdir-objects
+
+bin_PROGRAMS = tacc
+tacc_SOURCES = tacc.c
+tacc_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libtac/include
+tacc_LDFLAGS = -L.libs -ltac $(OPENSSL_LIBS)
+dist_man_MANS = tacc.1 tacplus_servers.5
+
+
+# This applies to all the libs, but it should only be for pam_tacplus.so
+moduledir = @pamdir@
+
+module_LTLIBRARIES = pam_tacplus.la
+lib_LTLIBRARIES = libtac.la
+
+libtac_includedir = $(oldincludedir)/tacplus
+
+libtac_include_HEADERS = \
+libtac/include/tacplus.h \
+libtac/include/libtac.h
+
+nodist_libtac_include_HEADERS = \
+libtac/include/cdefs.h
+
+libtac_la_SOURCES = \
+libtac/lib/acct_r.c \
+libtac/lib/acct_s.c \
+libtac/lib/attrib.c \
+libtac/lib/authen_r.c \
+libtac/lib/authen_s.c \
+libtac/lib/author_r.c \
+libtac/lib/author_s.c \
+libtac/lib/connect.c \
+libtac/lib/cont_s.c \
+libtac/lib/crypt.c \
+libtac/lib/hdr_check.c \
+libtac/lib/header.c \
+libtac/lib/magic.c \
+libtac/lib/magic.h \
+libtac/lib/md5.c \
+libtac/lib/md5.h \
+libtac/lib/messages.c \
+libtac/lib/messages.h \
+libtac/lib/read_wait.c \
+libtac/lib/version.c \
+libtac/lib/xalloc.c \
+libtac/lib/xalloc.h \
+$(libtac_include_HEADERS)
+libtac_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libtac/include
+libtac_la_LIBADD = -lpthread $(OPENSSL_LIBS)
+libtac_la_LDFLAGS = -version-info 2:0:0 -shared -Wl,--no-undefined \
+ -export-symbols-regex '^tac_'
+
+
+pam_tacplus_la_SOURCES = pam_tacplus.h \
+pam_tacplus.c \
+support.h \
+support.c
+pam_tacplus_la_CFLAGS = $(AM_CFLAGS) -I $(top_srcdir)/libtac/include
+pam_tacplus_la_LDFLAGS = -module -avoid-version -shared
+pam_tacplus_la_LIBADD = -L.libs -ltac -ltacplus_map -lpam $(OPENSSL_LIBS)
+# no versioning for this, since it's always installed as pam_tacplus.so
+# and not ever used as a link library
+
+EXTRA_DIST = pam_tacplus.spec libtac.pc.in
+if DOC
+dist_doc_DATA = sample.pam README.md AUTHORS ChangeLog
+endif
+
+MAINTAINERCLEANFILES = Makefile.in config.h.in configure aclocal.m4 \
+ config/config.guess config/config.sub config/depcomp \
+ config/install-sh config/ltmain.sh config/missing
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA = libtac.pc
+
+clean-generic:
+ rm -rf autom4te*.cache
+ rm -f *.rej *.orig *.lang
+
+install-data-hook:
+ ${INSTALL} -d $(DESTDIR)$(sysconfdir)
+ ${INSTALL} -m 600 tacplus_servers $(DESTDIR)$(sysconfdir)
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..73e0f18
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
+Check README
diff --git a/Pam.d.common-example b/Pam.d.common-example
new file mode 100644
index 0000000..a4064a1
--- /dev/null
+++ b/Pam.d.common-example
@@ -0,0 +1,53 @@
+
+These are example changes for the /etc/pam.d/common-* files to use pam_tacplus
+in the enhanced mode. I have have been unsuccessful (on ubuntu 14 or on debian
+wheezy or jessie), in using pam-auth-update files, because they get inserted at
+the incorrect locations. You also need to be sure that these changes do not
+apply to su and sudo, because they must not reset the auid and sessionid.
+
+For all of these, debug is optional (but you'll probably want it during setup, unless you
+are really lucky and everything just works).
+
+I've done all my testing against the linux tacacs+ server (debian wheezy package) with
+nothing special in the configuration, other than adding some users.
+
+I recommend not using pam_loginuid.so with pam_tacplus, unless you have your
+kernel set to make the loginuid immutable (indicated by auditctl supporting
+the -loginuid-immutable option.
+
+The file debian/tacplus is appropriate for use with pam-auth-update.
+
+It's setup to only try tacacs for "user" accounts. If you change that (here
+in the source, or on an end-system), be sure to do something to at least allow
+root (uid==0) to login when the tacacs server is down or not reachable.
+The rules are set so that if tacacs doesn't succeed it fall through to normal
+unix method.
+
+The reason we check > 1000 is normally linux non-privileged userid's start
+at 1000, and it's reasonable to assume a system has at least one non-privileged
+local user, and not checking the local user is a performance win.
+Change to suit your conventions. To minimize system impact, what
+you really should use is the range of uid's for your tacacs0..15 accounts
+
+Note, the "success=2" below is dependent on what your pam.d files
+look like. I recommend using pam-auth-update, which uses "success=end",
+with pam-auth-update replacing "end" with the appropriate number of
+rules to skip.
+
+ auth [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet
+ auth [authinfo_unavail=ignore success=2 default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login protocol=ssh service=shell
+
+ account [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet
+ account [authinfo_unavail=ignore success=2 perm_denied=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login protocol=ssh service=shell
+
+ session [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet
+ session [authinfo_unavail=ignore success=done default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login protocol=ssh service=shell
+
+/etc/tacplus_servers will have the list of tacacs+ servers and their shared secret. E.g:
+=====
+secret=tacacskey
+server=192.168.1.1
+=====
+
+It can also have any other options common to other libraries or clients that
+want to parse it, such as debugging options.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..390d23f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,191 @@
+[![Analysis Status](https://scan.coverity.com/projects/5499/badge.svg)](https://scan.coverity.com/projects/5499)
+
+# pam_tacplus
+
+This PAM module support the following functions:
+
+* authentication
+* authorization (account management)
+* accounting (session management)
+
+All are performed using TACACS+ protocol [1], designed by Cisco Systems.
+This is remote AAA protocol, supported by most Cisco hardware.
+A free TACACS+ server is available [2], which I'm using without any
+major problems for about a year. Advantages of TACACS+ is that all
+(unlike RADIUS) packets exchanged with the authentication server are
+encrypted. This module is an attempt to provide most useful part of
+TACACS+ functionality to applications using the PAM interface on Linux.
+
+Persistent connections are not supported, because libtac is single threaded.
+You must make a new connection via tac_connect_single() or equivalent for each
+new accounting, authorization, or authentication request.
+
+
+### Recognized options:
+
+| Option | Management group | Description |
+|------------------- | ---------------- | ----------- |
+| debug | ALL | output debugging information via syslog(3); note, that the debugging is heavy, including passwords! |
+| secret=STRING | ALL | can be specified more than once; secret key used to encrypt/decrypt packets sent/received from the server |
+| server=HOSTNAME server=IP_ADDR server=HOSTNAME:PORT server=IP_ADDR:PORT | auth, session | can be specified more than once; adds a TACACS+ server to the servers list |
+| timeout=INT | ALL | connection timeout in seconds default is 5 seconds |
+| login=STRING | auth | TACACS+ authentication service, this can be "shell", "pap", "chap" or "login" at the moment. Default is pap. |
+| prompt=STRING | auth | Custom password prompt. If you want to use a space use '_' character instead. |
+| acct_all | session | if multiple servers are supplied, pam_tacplus will send accounting start/stop packets to all servers on the list |
+| service | account, session | TACACS+ service for authorization and accounting |
+| protocol | account, session | TACACS+ protocol for authorization and accounting |
+
+The last two items are widely described in TACACS+ draft [1]. They are
+required by the server, but it will work if they don't match the real
+service authorized :)
+During PAM account the AV pairs returned by the TACACS+ servers are made available to the
+PAM environment, so you can use i.e. pam_exec.so to do something with these AV pairs.
+Not all service types return AV pairs. If you need privilege levels
+for accounting, e.g., at least some servers require the service to be "shell"
+
+### Basic installation:
+This project is using autotools for building, so please run autoreconf first.
+```
+$ autoreconf -i
+$ ./configure && make && sudo make install
+```
+
+### Example configuration
+ (This will be different with systems such as Debian Wheezy and recent
+ Ubuntu that use the common-* configfile method pam-auth-update).
+
+ Also see Pam.d.common-example for examples with user mapping, and more
+ comments.
+
+ Common parameters can also be set in /etc/tacplus_servers, rather than
+ the commandline by using the include=/etc/tacplus_servers paramter.
+ For the secret parameter, this also improves security
+
+
+```
+#%PAM-1.0
+auth required /lib/security/pam_tacplus.so debug server=1.1.1.1 secret=SECRET-1
+account required /lib/security/pam_tacplus.so debug secret=SECRET-1 service=ppp protocol=lcp
+account sufficient /lib/security/pam_exec.so /usr/local/bin/showenv.sh
+password required /lib/security/pam_cracklib.so
+password required /lib/security/pam_pwdb.so shadow use_authtok
+session required /lib/security/pam_tacplus.so debug server=1.1.1.1 server=2.2.2.2 secret=SECRET-1 secret=SECRET-2 service=ppp protocol=lcp
+```
+
+If you need AV attributes back, such as privilege level, then for
+some servers, you'll need to use service=shell for "account"
+
+
+### More on server lists:
+
+1. Having more that one TACACS+ server defined for given management group
+has following effects on authentication:
+
+ * if the first server on the list is unreachable or failing
+ pam_tacplus will try to authenticate the user against the other
+ servers until it succeeds
+
+ * the `first_hit' option has been deprecated
+
+ * when the authentication function gets a positive reply from
+ a server, it saves its address for future use by account
+ management function (see below)
+
+2. The account management (authorization) function asks *only one*
+TACACS+ server and it ignores the whole server list passed from command
+line. It uses server saved by authentication function after successful
+authenticating user on that server. We assume that the server is
+authoriative for queries about that user.
+
+3. The session management (accounting) functions obtain their server lists
+independently from the other functions. This allows you to account user
+sessions on different servers than those used for authentication and
+authorization.
+
+ * normally, without the `acct_all' modifier, the extra servers
+ on the list will be considered as backup servers, mostly like
+ in point 1. i.e. they will be used only if the first server
+ on the list will fail to accept our accounting packets
+
+ * with `acct_all' pam_tacplus will try to deliver the accounting
+ packets to all servers on the list; failure of one of the servers
+ will make it try another one
+
+ this is useful when your have several accounting, billing or
+ logging hosts and want to have the accounting information appear
+ on all of them at the same time
+
+
+### Short introduction to PAM via TACACS+:
+
+This diagram should show general idea of how the whole process looks:
+
+```
+ +-----+
+ Authen -user/pass valid?----------> | T S |
+ / | A e |
+ PAM- Author -service allowed?----------> | C r |
+ ^ \ | A v |
+ | Acct ,-----start session----------> | C e |
+ | `----stop session-----------> | S r |
+ Application +-----+
+
+ *Client Host* *Network* *Server Host*
+```
+
+Consider `login' application:
+
+1. Login accepts username and password from the user.
+2. Login calls PAM function pam_authenticate() to verify if the
+ supplied username/password pair is valid.
+3. PAM loads pam_tacplus module (as defined in /etc/pam.d/login)
+ and calls pam_sm_authenticate() function supplied by this module.
+4. This function sends an encrypted packet to the TACACS+ server.
+ The packet contains username and password to verify. TACACS+ server
+ replied with either positive or negative response. If the reponse
+ is negative, the whole thing is over ;)
+5. PAM calls another function from pam_tacplus - pam_sm_acct_mgmt().
+ This function is expected to verify whether the user is allowed
+ to get the service he's requesting (in this case: unix shell).
+ The function again verifies the permission on TACACS+ server. Assume
+ the server granted the user with requested service.
+6. Before user gets the shell, PAM calls one another function from
+ pam_tacplus - pam_sm_open_session(). This results in sending an
+ accounting START packet to the server. Among other things it contains
+ the terminal user loggen in on and the time session started.
+7. When user logs out, pam_sm_close_session() sends STOP packet to the
+ server. The whole session is closed.
+
+### TACACS+ client program
+The library comes with a simple TACACS+ client program `tacc` which can be used for testing as well as simple scripting. Sample usage:
+
+```
+tacc --authenticate --authorize --account --username test1
+ --password test1 --server localhost --remote 1.1.1.1
+ --secret test1 --service ppp --protocol ip
+```
+This configuration runs full AAA round (authentication, authorization and accounting). The `server` and `secret` option specify server connection parameters and all remaining options supply data specific to TACACS+ protocol. The `tac_plus` daemon (found in `tacacs+` package in Debian and Ubuntu) can be used for testing with the following example configuration:
+```
+key = test1
+user = test1 {
+ global = cleartext "test1"
+ service = ppp protocol = ip {
+ addr=8.8.8.8
+ }
+}
+```
+
+### Limitations:
+
+Many of them for now :)
+
+* only subset of TACACS+ protocol is supported; it's enough for most need, though
+* utilize PAM_SERVICE item obtained from PAM for TACACS+ services
+* clean options and configuration code
+
+### Authors:
+
+Pawel Krawczyk <pawel.krawczyk@hush.com>
+https://ipsec.pl/
+
+Jeroen Nijhof <jeroen@jeroennijhof.nl>
diff --git a/auto.sh b/auto.sh
new file mode 100755
index 0000000..700d120
--- /dev/null
+++ b/auto.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+mkdir -p config
+autoreconf -f -v -i
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..fae4809
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,114 @@
+dnl
+dnl File: configure.in
+dnl Revision: $Id: configure.ac,v 1.4 2010/06/11 12:04:29 j-nijhof Exp $
+dnl Created: 2010/06/09
+dnl Author: Jeroen Nijhof <jeroen@jeroennijhof.nl>
+dnl Benoit Donneaux <benoit.donneaux@gmail.com>
+dnl
+dnl Process this file with autoconf to produce a configure script
+dnl You need autoconf 2.59 or better!
+dnl
+dnl ---------------------------------------------------------------------------
+
+AC_PREREQ(2.59)
+AC_COPYRIGHT([
+See the included file: COPYING for copyright information.
+])
+AC_INIT(pam_tacplus, 1.4.0, [jeroen@jeroennijhof.nl,pawel.krawczyk@hush.com])
+
+AC_CONFIG_AUX_DIR(config)
+AM_INIT_AUTOMAKE([foreign])
+AC_CONFIG_SRCDIR([pam_tacplus.c])
+AC_CONFIG_HEADER([config.h])
+AC_CONFIG_MACRO_DIR([config])
+
+dnl --------------------------------------------------------------------
+dnl Checks for programs.
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+LT_INIT([disable-static])
+AM_PROG_CC_C_O
+
+dnl --------------------------------------------------------------------
+dnl Checks for libraries, but don't use -lpam for everything
+AC_CHECK_LIB(pam, pam_start, HAVE_LIBPAM=1)
+AC_CHECK_LIB(logwtmp)
+
+case "$host" in
+ sparc-* | sparc64-*)
+ LIBS="$LIBS -lresolv";;
+esac
+
+dnl --------------------------------------------------------------------
+dnl Checks for header files.
+AC_HEADER_STDC
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h string.h strings.h sys/socket.h sys/time.h syslog.h unistd.h])
+AC_CHECK_HEADER(security/pam_appl.h, [], [AC_MSG_ERROR([PAM libraries missing. Install with "yum install pam-devel" or "apt-get install libpam-dev".])] )
+
+dnl --------------------------------------------------------------------
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_CONST
+AC_TYPE_SIZE_T
+AC_HEADER_TIME
+
+AC_ENABLE_SHARED(yes)
+AC_ENABLE_STATIC(no)
+AC_DISABLE_STATIC
+
+AC_ARG_ENABLE(am-ldcflags, AS_HELP_STRING([--disable-am-ldcflags], [do not add various 'AM_CFLAGS/AM_LDFLAGS']))
+if test "x$enable_am_ldcflags" != "xno"; then
+ for flag in -fstack-protector-all -Wl,-z,relro -Wl,-z,now -fPIE -pie -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2; do
+ my_save_cflags="$CFLAGS"
+ CFLAGS=$flag
+ AC_MSG_CHECKING([whether CC supports $flag])
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])],
+ [AC_MSG_RESULT([yes])]
+ [AM_CFLAGS="$AM_CFLAGS $flag"]
+ [AM_LDFLAGS="$AM_LDFLAGS $flag"],
+ [AC_MSG_RESULT([no])]
+ )
+ CFLAGS="$my_save_cflags"
+ done
+ AC_SUBST([AM_CFLAGS])
+ AC_SUBST([AM_LDFLAGS])
+fi
+
+dnl --------------------------------------------------------------------
+dnl Checks for library functions.
+AC_FUNC_REALLOC
+AC_FUNC_SELECT_ARGTYPES
+AC_TYPE_SIGNAL
+AC_CHECK_FUNCS([bzero gethostbyname gettimeofday inet_ntoa select socket logwtmp])
+
+dnl --------------------------------------------------------------------
+dnl Switch for pam module dir
+AC_ARG_ENABLE([pamdir], [AS_HELP_STRING([--enable-pamdir],
+ [Location to install the pam module ($libdir/security)])],
+ [pamdir=$enableval], [pamdir=$libdir/security])
+AC_SUBST(pamdir)
+
+AC_ARG_ENABLE(doc, AS_HELP_STRING([--disable-doc], [do not build docs]))
+AM_CONDITIONAL(DOC, test "x$enable_doc" != "xno")
+
+AC_ARG_ENABLE(openssl,[ --disable-openssl disables OpenSSL random number, etc],
+ [
+ if test "x$enableval" = "xyes" ; then
+ AX_CHECK_OPENSSL([
+ AC_CHECK_LIB(crypto, MD5_Init)
+ AC_CHECK_LIB(crypto, RAND_pseudo_bytes)
+ AC_CHECK_HEADERS([ openssl/md5.h openssl/rand.h])]
+ ,[AC_MSG_ERROR([No openssl found.])])
+ fi
+ ],[
+ AX_CHECK_OPENSSL(,[AC_MSG_ERROR([No openssl found. using builtin crypto])])
+])
+
+
+dnl --------------------------------------------------------------------
+dnl Generate made files
+AC_CONFIG_FILES([Makefile
+ libtac.pc
+ pam_tacplus.spec])
+AC_OUTPUT
diff --git a/debian/README.Debian b/debian/README.Debian
new file mode 100644
index 0000000..418c18f
--- /dev/null
+++ b/debian/README.Debian
@@ -0,0 +1,18 @@
+libpam-tacplus for Debian
+---------------------
+
+The pam_tacplus.so module is placed in /lib/security/
+to include pam_tacplus.so edit /etc/pam.d/common-*
+
+Run script -c "dpkg-buildpackage" as root
+in the source directory above this one.
+
+You will need at least the debhelper and libpam0g-dev
+packages.
+
+Look at the content list of the deb file with "dpkg -c"
+
+Change the version number by running "debchange -i" and add in the NEWS
+entries for the given version.
+
+ -- J. Nijhof <jeroen@jeroennijhof.nl>, Sun, 14 Feb 2010
diff --git a/debian/README.source b/debian/README.source
new file mode 100644
index 0000000..68089c6
--- /dev/null
+++ b/debian/README.source
@@ -0,0 +1,5 @@
+This package uses quilt to manage all modifications to the upstream source.
+Changes are stored in the source package as diffs in debian/patches and
+applied during the build.
+
+See /usr/share/doc/quilt/README.source for a detailed explanation.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..2baea8f
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,114 @@
+libpam-tacplus (1.4.3-cl3u4) RELEASED; urgency=low
+ * Closes: CM-23004 - local account password was allowed even when
+ TACACS+ server could be reached and was authoritative for the accountname
+
+ -- dev-support <dev-support@cumulusnetworks.com> Thu, 15 Nov 2018 16:44:44 -0800
+
+libpam-tacplus (1.4.3-cl3u3) RELEASED; urgency=low
+ * minor bug fixes
+
+ -- dev-support <dev-support@cumulusnetworks.com> Thu, 16 Aug 2018 13:27:39 -0700
+
+libpam-tacplus (1.4.3-cl3u2) RELEASED; urgency=low
+ * Optimized attempts to connect to server that has previously not responded
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 03 Jul 2018 17:06:21 -0700
+
+libpam-tacplus (1.4.3-cl3u1) RELEASED; urgency=low
+ * New: Enabled - added the ability to set the source IP address via
+ the source_ip config variable.
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 03 Jul 2018 17:04:58 -0700
+
+libpam-tacplus (1.4.2-cl3u4) RELEASED; urgency=low
+ * Fixed error message when creating home directory
+ * Closes CM-19908 - Logging changed to use pam_syslog, log message format
+ now has pam_tacplus and program invoking, not PAM-tacplus
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 27 Feb 2018 13:09:58 -0800
+
+libpam-tacplus (1.4.2-cl3u3) RELEASED; urgency=low
+ * do not log message about acct_all unknown config variable
+
+ -- dev-support <dev-support@cumulusnetworks.com> Thu, 28 Sep 2017 14:10:58 -0700
+
+libpam-tacplus (1.4.2-cl3u2) RELEASED; urgency=low
+ * Closes: CM-16962: protocol and cmd attributes added multiple times
+
+ -- dev-support <dev-support@cumulusnetworks.com> Thu, 29 Jun 2017 19:47:06 -0700
+
+libpam-tacplus (1.4.2-cl3u1) RELEASED; urgency=low
+ * New Disabled: added user_homedir config variable to allow per-user
+ home directories (unless per-command authorization is enabled)
+ * New: Added tacplus_servers manual page
+ * Fixed problem with PAM configuration that allowed login when
+ authentication was successful, but authorization was denied.
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 23 May 2017 18:07:54 -0700
+
+libpam-tacplus (1.4.1-cl3u1) RELEASED; urgency=low
+ * New Enabled: vrf config option in tacplus_servers.
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 07 Mar 2017 14:47:51 -0800
+
+libpam-tacplus (1.4.0-cl3u2) RELEASED; urgency=low
+ * New Enabled: added config variable "timeout" to limit time attempting to
+ connect to non-responding TACACS server.
+ * Closes: CM-13548: Fixed PAM configuration to continue to other PAM modules
+ * Fixed issues with ordering of multiple servers and secrets in config files.
+ libraries can connect to a TACACS+ server without being tacacs aware.
+ * Fixed to try all TACACS servers until a successful status is returned,
+ in case servers have different databases.
+ * Minor bug fixes and syslog debugging improvements.
+ * Minor corrections to Copyright and licensing
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 29 Nov 2016 17:23:18 -0800
+
+libpam-tacplus (1.4.0-cl3eau1) RELEASED; urgency=medium
+ * Added the runtime config capability to include another file, so that
+ the tacacs servers are only listed in a single place. Ship using
+ /etc/tacplus_servers as an include file, and use it in the pam sample config
+ * Modified packaging to create separate libtac package.
+
+ -- dev-support <dev-support@cumulusnetworks.com> Tue, 21 Jun 2016 11:03:21 -0700
+
+libpam-tacplus (1.4.0-1) unstable; urgency=medium
+ * use OpenSSL for MD5 and random numbers
+
+ -- Paweł Krawczyk <pawel.krawczyk@hush.com> Tue, 3 May 2016 12:51:09 +0100
+
+libpam-tacplus (1.3.8-2) unstable; urgency=low
+
+ * Added postinst and prerm scripts for pam-auth-update. Closes: #739274
+
+ -- Jeroen Nijhof <jeroen@jeroennijhof.nl> Mon, 17 Feb 2014 18:58:59 +0100
+
+libpam-tacplus (1.3.8-1) unstable; urgency=low
+
+ * New upstream release.
+ * Fixed pam-configs. Closes: #717716
+ * Added dh-autoreconf. Closes: #734228
+
+ -- Jeroen Nijhof <jeroen@jeroennijhof.nl> Fri, 31 Jan 2014 12:32:00 +0100
+
+libpam-tacplus (1.3.7-1) unstable; urgency=low
+
+ * New upstream release.
+ * Changed compat level to 9 for hardening
+ * Fixed license link
+
+ -- Jeroen Nijhof <jeroen@jeroennijhof.nl> Mon, 19 May 2012 19:25:00 +0100
+
+libpam-tacplus (1.3.6-1) unstable; urgency=low
+
+ * New upstream release.
+ * Added libpam-runtime support.
+
+ -- Jeroen Nijhof <jeroen@jeroennijhof.nl> Mon, 7 May 2012 21:21:00 +0100
+
+libpam-tacplus (1.3.5-1) unstable; urgency=low
+
+ * First version of pam_tacplus debian package. Closes: #588172
+
+ -- Jeroen Nijhof <jeroen@jeroennijhof.nl> Mon, 5 Sep 2011 16:01:00 +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..2e1d246
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,47 @@
+Source: libpam-tacplus
+Section: admin
+Priority: extra
+Build-Depends: debhelper (>= 9), libpam-dev, dh-autoreconf, autoconf-archive, libaudit-dev,
+ libtacplus-map-dev, git, libssl-dev
+Maintainer: dev-support <dev-support@cumulusnetworks.com>
+Standards-Version: 3.9.6
+#Homepage: https://github.com/jeroennijhof/pam_tacplus
+Homepage: http://www.cumulusnetworks.com
+
+Package: libpam-tacplus
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, libpam-runtime, libtac2, libtacplus-map1
+Description: PAM module for using TACACS+ as an authentication service
+ This PAM module support authentication, authorization (account management) and
+ accounting (session management) performed using TACACS+ protocol designed by
+ Cisco.
+
+Package: libpam-tacplus-dev
+Section: libdevel
+Architecture: any
+Depends: ${misc:Depends}, libpam-tacplus (= ${binary:Version}), libc6-dev|libc-dev
+Description: Development files for PAM module for using TACACS+ authentication
+ Provides header files for development with libpam-tacplus
+
+Package: libtac2
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, libaudit1
+Description: TACACS+ protocol library
+ This library implemenents the fundamentls of the TACACS+ protocol and supports
+ authentication, authorization (account management) and accounting (session
+ management).
+
+Package: libtac2-bin
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: TACACS+ client program
+ Simple command-line client for TACACS+ testing and scripting
+
+Package: libtac-dev
+Section: libdevel
+Architecture: any
+Depends: ${misc:Depends}, libtac2 (= ${binary:Version}), libc6-dev|libc-dev
+Description: Development files for TACACS+ protocol library
+ Contains C header files and development files for libtac, a TACACS+ protocol
+ implementation.
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..5c1c328
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,30 @@
+
+libpam-tacplus
+
+ Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com>
+ and Jeroen Nijhof <jeroen@jeroennijhof.nl>.
+
+ Copyright 2015, 2016, Cumulus Networks Inc. All rights reserved.
+
+ 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 St, Fifth Floor, Boston,
+ MA 02110-1301, USA.
+
+All the other scripts and control files for building and installing
+libpam-tacplus under Debian GNU/Linux are also under the GNU General Public
+License (GPL) version 2 or later.
+
+On Debian GNU/Linux systems, the complete text of the GNU General
+Public License can be found in '/usr/share/common-licenses/GPL-2'.
+
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..4d97eb1
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1,2 @@
+README.md
+sample.pam
diff --git a/debian/libpam-tacplus-dev.install b/debian/libpam-tacplus-dev.install
new file mode 100644
index 0000000..8f580bf
--- /dev/null
+++ b/debian/libpam-tacplus-dev.install
@@ -0,0 +1 @@
+usr/include/tacplus/tacplus.h
diff --git a/debian/libpam-tacplus.install b/debian/libpam-tacplus.install
new file mode 100644
index 0000000..9b356e5
--- /dev/null
+++ b/debian/libpam-tacplus.install
@@ -0,0 +1,4 @@
+lib/*/security/*.so
+usr/share/doc/libpam-tacplus/sample.pam
+etc/tacplus_servers
+usr/share/man/man5/*
diff --git a/debian/libpam-tacplus.postinst b/debian/libpam-tacplus.postinst
new file mode 100644
index 0000000..7e37590
--- /dev/null
+++ b/debian/libpam-tacplus.postinst
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+set -e
+
+pam-auth-update --package
+
+#DEBHELPER#
diff --git a/debian/libpam-tacplus.prerm b/debian/libpam-tacplus.prerm
new file mode 100644
index 0000000..e143dcb
--- /dev/null
+++ b/debian/libpam-tacplus.prerm
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -e
+
+if [ "$1" = remove ]; then
+ pam-auth-update --package --remove tacplus
+fi
+
+#DEBHELPER#
diff --git a/debian/libtac-dev.install b/debian/libtac-dev.install
new file mode 100644
index 0000000..67ab0da
--- /dev/null
+++ b/debian/libtac-dev.install
@@ -0,0 +1,3 @@
+usr/lib/*/libtac.so
+usr/lib/*/pkgconfig/libtac.pc
+usr/include/tacplus/libtac.h
diff --git a/debian/libtac2-bin.install b/debian/libtac2-bin.install
new file mode 100644
index 0000000..68671de
--- /dev/null
+++ b/debian/libtac2-bin.install
@@ -0,0 +1,2 @@
+usr/bin/*
+usr/share/man/man1/*
diff --git a/debian/libtac2.install b/debian/libtac2.install
new file mode 100644
index 0000000..497d9dd
--- /dev/null
+++ b/debian/libtac2.install
@@ -0,0 +1 @@
+usr/lib/*/libtac.so.*
diff --git a/debian/libtac2.symbols b/debian/libtac2.symbols
new file mode 100644
index 0000000..3f3423a
--- /dev/null
+++ b/debian/libtac2.symbols
@@ -0,0 +1,33 @@
+libtac.so.2 libtac2 #MINVER#
+ tac_acct_flag2str@Base 1.3.9
+ tac_acct_read@Base 1.3.9
+ tac_acct_send@Base 1.3.9
+ tac_add_attrib@Base 1.3.9
+ tac_add_attrib_pair@Base 1.3.9
+ tac_authen_method@Base 1.3.9
+ tac_authen_read@Base 1.3.9
+ tac_authen_send@Base 1.3.9
+ tac_authen_service@Base 1.3.9
+ tac_author_read@Base 1.3.9
+ tac_author_send@Base 1.3.9
+ tac_connect@Base 1.3.9
+ tac_connect_single@Base 1.3.9
+ tac_cont_send_seq@Base 1.3.9
+ tac_debug_enable@Base 1.3.9
+ tac_encryption@Base 1.3.9
+ tac_free_attrib@Base 1.3.9
+ tac_login@Base 1.3.9
+ tac_magic@Base 1.4.0
+ tac_ntop@Base 1.3.9
+ tac_priv_lvl@Base 1.3.9
+ tac_read_wait@Base 1.3.9
+ tac_readtimeout_enable@Base 1.3.9
+ tac_secret@Base 1.3.9
+ tac_timeout@Base 1.3.9
+ tac_ver_major@Base 1.3.9
+ tac_ver_minor@Base 1.3.9
+ tac_ver_patch@Base 1.3.9
+ tac_xcalloc@Base 1.3.9
+ tac_xrealloc@Base 1.3.9
+ tac_xstrcpy@Base 1.3.9
+ tac_xstrdup@Base 1.3.9
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..04c9649
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,30 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+SHELL := sh -e
+
+%:
+ dh $@ --with autoreconf
+
+override_dh_clean:
+ rm -f config.status config.log
+ dh_clean
+
+override_dh_auto_configure:
+ dh_auto_configure -- --enable-pamdir=/lib/$(DEB_HOST_MULTIARCH)/security --docdir=/usr/share/doc/libpam-tacplus --disable-openssl
+
+override_dh_install:
+ mkdir -p debian/libpam-tacplus/usr/share/pam-configs
+ cp debian/tacplus debian/libpam-tacplus/usr/share/pam-configs/
+ dh_install
+
+# tacplus_servers needs to be mode 600; the install sets it that way,
+# so keep dh_fixperms from "fixing" it.
+override_dh_fixperms:
+ dh_fixperms --exclude tacplus_servers
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..b9b0237
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1,2 @@
+1.0
+
diff --git a/debian/tacplus b/debian/tacplus
new file mode 100644
index 0000000..f81204f
--- /dev/null
+++ b/debian/tacplus
@@ -0,0 +1,15 @@
+Name: Tacacs+ authentication
+Default: yes
+Priority: 257
+Auth-Type: Primary
+Auth:
+ [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet
+ [authinfo_unavail=ignore success=end auth_err=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login protocol=ssh service=shell
+Account-Type: Primary
+Account:
+ [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet
+ [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login protocol=ssh service=shell
+Session-Type: Additional
+Session:
+ [default=1 success=ignore] pam_succeed_if.so uid > 1000 quiet
+ [authinfo_unavail=ignore success=ok default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login protocol=ssh service=shell
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 0000000..d73b611
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,3 @@
+version=3
+
+https://github.com/jeroennijhof/pam_tacplus/tags .*/v?(\d[\d\.]+)\.tar\.gz
diff --git a/libtac.pc.in b/libtac.pc.in
new file mode 100644
index 0000000..eb3030c
--- /dev/null
+++ b/libtac.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/libtac
+
+Name: libtac
+Description: A TACACS+ protocol implementation
+URL: https://github.com/jeroennijhof/pam_tacplus
+Version: @VERSION@
+Libs: -L${libdir} -ltac
+Cflags: -I${includedir}
diff --git a/libtac/include/cdefs.h b/libtac/include/cdefs.h
new file mode 100644
index 0000000..13d4ad5
--- /dev/null
+++ b/libtac/include/cdefs.h
@@ -0,0 +1,62 @@
+/* cdefs.h
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef _CDEFS_H
+#define _CDEFS_H
+
+#undef __P
+#if defined(__STDC__) || defined(__cplusplus)
+#define __P(p) p
+#else
+#define __P(p)
+#endif
+#define _PTR void *
+#define _ANDi ,
+#define _NOARGS void
+#define _CONST const
+#define _VOLATILE volatile
+#define _SIGNED signed
+#define _DOTS , ...
+#define _VOID void
+#define _EXFUN(name, proto) name proto
+#define _DEFUN(name, arglist, args) name(args)
+#define _DEFUN_VOID(name) name(_NOARGS)
+#define _CAST_VOID (void)
+#ifndef _LONG_DOUBLE
+#define _LONG_DOUBLE long double
+#endif
+#ifndef _PARAMS
+#define _PARAMS(paramlist) paramlist
+#endif
+
+/* Support gcc's __attribute__ facility. */
+
+#define _ATTRIBUTE(attrs) __attribute__ ((attrs))
+
+#if defined(__cplusplus)
+#define __BEGIN_DECLS extern "C" {
+#define __END_DECLS }
+#else
+#define __BEGIN_DECLS
+#define __END_DECLS
+#endif
+
+#endif
diff --git a/libtac/include/libtac.h b/libtac/include/libtac.h
new file mode 100644
index 0000000..9573fe4
--- /dev/null
+++ b/libtac/include/libtac.h
@@ -0,0 +1,173 @@
+/* libtac.h
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef _LIB_TAC_H
+#define _LIB_TAC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifdef __linux__
+#include <sys/cdefs.h>
+#else
+#include "cdefs.h"
+#endif
+#include "tacplus.h"
+
+#if defined(DEBUGTAC) && !defined(TACDEBUG)
+#define TACDEBUG(x) syslog x;
+#else
+//#define TACDEBUG(x) syslog x;
+#define TACDEBUG(x)
+#endif
+
+#define TACSYSLOG(x) syslog x;
+
+#if defined(TACDEBUG_AT_RUNTIME)
+#undef TACDEBUG
+#undef TACSYSLOG
+#define TACDEBUG(x) if (tac_debug_enable) (void)logmsg x;
+#define TACSYSLOG(x) (void)logmsg x;
+extern int logmsg __P((int, const char*, ...));
+#endif
+
+/* u_int32_t support for sun */
+#ifdef sun
+typedef unsigned int u_int32_t;
+#endif
+
+struct tac_attrib {
+ char *attr;
+ u_char attr_len;
+ struct tac_attrib *next;
+};
+
+struct areply {
+ struct tac_attrib *attr;
+ char *msg;
+ int status : 8;
+ int flags : 8;
+ int seq_no : 8;
+};
+
+#ifndef TAC_PLUS_MAXSERVERS
+#define TAC_PLUS_MAXSERVERS 8
+#endif
+
+#ifndef TAC_PLUS_MAX_PACKET_SIZE
+#define TAC_PLUS_MAX_PACKET_SIZE 128000 /* bytes */
+#endif
+
+#ifndef TAC_PLUS_PORT
+#define TAC_PLUS_PORT 49
+#endif
+
+#define TAC_PLUS_READ_TIMEOUT 180 /* seconds */
+#define TAC_PLUS_WRITE_TIMEOUT 180 /* seconds */
+
+/* Internal status codes
+ * all negative, tacplus status codes are >= 0
+ */
+
+#define LIBTAC_STATUS_ASSEMBLY_ERR -1
+#define LIBTAC_STATUS_PROTOCOL_ERR -2
+#define LIBTAC_STATUS_READ_TIMEOUT -3
+#define LIBTAC_STATUS_WRITE_TIMEOUT -4
+#define LIBTAC_STATUS_WRITE_ERR -5
+#define LIBTAC_STATUS_SHORT_HDR -6
+#define LIBTAC_STATUS_SHORT_BODY -7
+#define LIBTAC_STATUS_CONN_TIMEOUT -8
+#define LIBTAC_STATUS_CONN_ERR -9
+#define LIBTAC_STATUS_CONN_CLOSED -10
+
+/* Runtime flags */
+
+/* version.c */
+extern int tac_ver_major;
+extern int tac_ver_minor;
+extern int tac_ver_patch;
+
+/* header.c */
+extern int session_id;
+extern int tac_encryption;
+extern const char *tac_secret;
+extern char tac_login[64];
+extern int tac_priv_lvl;
+extern int tac_authen_method;
+extern int tac_authen_service;
+
+extern int tac_debug_enable;
+extern int tac_readtimeout_enable;
+
+/* connect.c */
+extern int tac_timeout;
+
+int tac_connect(struct addrinfo **, char **, int, char *iface);
+int tac_connect_single(struct addrinfo *, const char *, struct addrinfo *,
+ char *iface);
+char *tac_ntop(const struct sockaddr *);
+
+int tac_authen_send(int, const char *, char *, char *,
+ char *, u_char);
+int tac_authen_read(int, struct areply *);
+int tac_cont_send_seq(int, char *, int);
+#define tac_cont_send(fd, pass) tac_cont_send_seq((fd), (pass), 3)
+HDR *_tac_req_header(u_char, int);
+void _tac_crypt(u_char *, HDR *, int);
+u_char *_tac_md5_pad(int, HDR *);
+void tac_add_attrib(struct tac_attrib **, char *, char *);
+void tac_free_attrib(struct tac_attrib **);
+char *tac_acct_flag2str(int);
+int tac_acct_send(int, int, const char *, char *, char *,
+ struct tac_attrib *);
+int tac_acct_read(int, struct areply *);
+void *tac_xcalloc(size_t, size_t);
+void *tac_xrealloc(void *, size_t);
+char *tac_xstrcpy(char *, const char *, size_t);
+char *tac_xstrdup(const char *);
+char *_tac_check_header(HDR *, int);
+int tac_author_send(int, const char *, char *, char *,
+ struct tac_attrib *);
+int tac_author_read(int, struct areply *);
+void tac_add_attrib_pair(struct tac_attrib **, char *, char,
+ char *);
+int tac_read_wait(int, int, int, int *);
+
+/* magic.c */
+u_int32_t tac_magic(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libtac/include/tacplus.h b/libtac/include/tacplus.h
new file mode 100644
index 0000000..fcee849
--- /dev/null
+++ b/libtac/include/tacplus.h
@@ -0,0 +1,231 @@
+/* tacplus.h
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef _TACPLUS_H
+#define _TACPLUS_H
+
+/* All tacacs+ packets have the same header format */
+struct tac_plus_pak_hdr {
+ u_char version;
+
+#define TAC_PLUS_MAJOR_VER_MASK 0xf0
+#define TAC_PLUS_MAJOR_VER 0xc0
+
+#define TAC_PLUS_MINOR_VER_0 0x00
+#define TAC_PLUS_VER_0 (TAC_PLUS_MAJOR_VER | TAC_PLUS_MINOR_VER_0)
+
+#define TAC_PLUS_MINOR_VER_1 0x01
+#define TAC_PLUS_VER_1 (TAC_PLUS_MAJOR_VER | TAC_PLUS_MINOR_VER_1)
+
+ u_char type;
+
+#define TAC_PLUS_AUTHEN 0x01
+#define TAC_PLUS_AUTHOR 0x02
+#define TAC_PLUS_ACCT 0x03
+
+ u_char seq_no; /* packet sequence number */
+ u_char encryption; /* packet is encrypted or cleartext */
+
+#define TAC_PLUS_ENCRYPTED_FLAG 0x00 /* packet is encrypted */
+#define TAC_PLUS_UNENCRYPTED_FLAG 0x01 /* packet is unencrypted */
+#define TAC_PLUS_SINGLE_CONNECT_FLAG 0x04 /* multiplexing supported */
+
+ int session_id; /* session identifier FIXME: Is this needed? */
+ int datalength; /* length of encrypted data following this
+ header datalength bytes of encrypted data */
+};
+
+#define TAC_PLUS_HDR_SIZE 12
+
+typedef struct tac_plus_pak_hdr HDR;
+
+/* Authentication packet NAS sends to us */
+struct authen_start {
+ u_char action;
+
+#define TAC_PLUS_AUTHEN_LOGIN 0x01
+#define TAC_PLUS_AUTHEN_CHPASS 0x02
+#define TAC_PLUS_AUTHEN_SENDPASS 0x03 /* deprecated */
+#define TAC_PLUS_AUTHEN_SENDAUTH 0x04
+
+ u_char priv_lvl;
+
+#define TAC_PLUS_PRIV_LVL_MIN 0x00
+#define TAC_PLUS_PRIV_LVL_MAX 0x0f
+#define TAC_PLUS_PRIV_LVL_USER 0x01
+#define TAC_PLUS_PRIV_LVL_ROOT 0x0f
+
+ u_char authen_type;
+
+#define TAC_PLUS_AUTHEN_TYPE_ASCII 0x01
+#define TAC_PLUS_AUTHEN_TYPE_PAP 0x02
+#define TAC_PLUS_AUTHEN_TYPE_CHAP 0x03
+#define TAC_PLUS_AUTHEN_TYPE_ARAP 0x04
+#define TAC_PLUS_AUTHEN_TYPE_MSCHAP 0x05
+
+ u_char service;
+
+#define TAC_PLUS_AUTHEN_SVC_NONE 0x00
+#define TAC_PLUS_AUTHEN_SVC_LOGIN 0x01
+#define TAC_PLUS_AUTHEN_SVC_ENABLE 0x02
+#define TAC_PLUS_AUTHEN_SVC_PPP 0x03
+#define TAC_PLUS_AUTHEN_SVC_ARAP 0x04
+#define TAC_PLUS_AUTHEN_SVC_PT 0x05
+#define TAC_PLUS_AUTHEN_SVC_RCMD 0x06
+#define TAC_PLUS_AUTHEN_SVC_X25 0x07
+#define TAC_PLUS_AUTHEN_SVC_NASI 0x08
+#define TAC_PLUS_AUTHEN_SVC_FWPROXY 0x09
+
+ u_char user_len;
+ u_char port_len;
+ u_char r_addr_len;
+ u_char data_len;
+};
+
+#define TAC_AUTHEN_START_FIXED_FIELDS_SIZE 8
+
+/* Authentication continue packet NAS sends to us */
+struct authen_cont {
+ u_short user_msg_len;
+ u_short user_data_len;
+ u_char flags;
+
+#define TAC_PLUS_CONTINUE_FLAG_ABORT 0x01
+
+};
+
+#define TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE 5
+
+/* Authentication reply packet we send to NAS */
+struct authen_reply {
+ u_char status;
+
+#define TAC_PLUS_AUTHEN_STATUS_PASS 0x01
+#define TAC_PLUS_AUTHEN_STATUS_FAIL 0x02
+#define TAC_PLUS_AUTHEN_STATUS_GETDATA 0x03
+#define TAC_PLUS_AUTHEN_STATUS_GETUSER 0x04
+#define TAC_PLUS_AUTHEN_STATUS_GETPASS 0x05
+#define TAC_PLUS_AUTHEN_STATUS_RESTART 0x06
+#define TAC_PLUS_AUTHEN_STATUS_ERROR 0x07
+#define TAC_PLUS_AUTHEN_STATUS_FOLLOW 0x21
+
+ u_char flags;
+
+#define TAC_PLUS_AUTHEN_FLAG_NOECHO 0x01
+
+ u_short msg_len;
+ u_short data_len;
+};
+
+#define TAC_AUTHEN_REPLY_FIXED_FIELDS_SIZE 6
+
+#define TAC_PLUS_AUTHEN_METH_NOT_SET 0x00
+#define TAC_PLUS_AUTHEN_METH_NONE 0x01
+#define TAC_PLUS_AUTHEN_METH_KRB5 0x02
+#define TAC_PLUS_AUTHEN_METH_LINE 0x03
+#define TAC_PLUS_AUTHEN_METH_ENABLE 0x04
+#define TAC_PLUS_AUTHEN_METH_LOCAL 0x05
+#define TAC_PLUS_AUTHEN_METH_TACACSPLUS 0x06
+#define TAC_PLUS_AUTHEN_METH_GUEST 0x08
+#define TAC_PLUS_AUTHEN_METH_RADIUS 0x10
+#define TAC_PLUS_AUTHEN_METH_KRB4 0x11
+#define TAC_PLUS_AUTHEN_METH_RCMD 0x20
+
+#define AUTHEN_METH_NONE TAC_PLUS_AUTHEN_METH_NONE
+#define AUTHEN_METH_KRB5 TAC_PLUS_AUTHEN_METH_KRB5
+#define AUTHEN_METH_LINE TAC_PLUS_AUTHEN_METH_LINE
+#define AUTHEN_METH_ENABLE TAC_PLUS_AUTHEN_METH_ENABLE
+#define AUTHEN_METH_LOCAL TAC_PLUS_AUTHEN_METH_LOCAL
+#define AUTHEN_METH_TACACSPLUS TAC_PLUS_AUTHEN_METH_TACACSPLUS
+#define AUTHEN_METH_RCMD TAC_PLUS_AUTHEN_METH_RCMD
+
+struct acct {
+ u_char flags;
+
+#define TAC_PLUS_ACCT_FLAG_MORE 0x01
+#define TAC_PLUS_ACCT_FLAG_START 0x02
+#define TAC_PLUS_ACCT_FLAG_STOP 0x04
+#define TAC_PLUS_ACCT_FLAG_WATCHDOG 0x08
+
+ u_char authen_method;
+ u_char priv_lvl;
+ u_char authen_type;
+ u_char authen_service;
+ u_char user_len;
+ u_char port_len;
+ u_char r_addr_len;
+ u_char arg_cnt; /* the number of cmd args */
+};
+
+#define TAC_ACCT_REQ_FIXED_FIELDS_SIZE 9
+
+struct acct_reply {
+ u_short msg_len;
+ u_short data_len;
+ u_char status;
+
+#define TAC_PLUS_ACCT_STATUS_SUCCESS 0x1
+#define TAC_PLUS_ACCT_STATUS_ERROR 0x2
+#define TAC_PLUS_ACCT_STATUS_FOLLOW 0x21
+
+};
+
+#define TAC_ACCT_REPLY_FIXED_FIELDS_SIZE 5
+
+/* An authorization request packet */
+struct author {
+ u_char authen_method;
+ u_char priv_lvl;
+ u_char authen_type;
+ u_char service;
+
+ u_char user_len;
+ u_char port_len;
+ u_char r_addr_len;
+ u_char arg_cnt; /* the number of args */
+};
+
+#define TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE 8
+
+/* An authorization reply packet */
+struct author_reply {
+ u_char status;
+ u_char arg_cnt;
+ u_short msg_len;
+ u_short data_len;
+
+#define TAC_PLUS_AUTHOR_STATUS_PASS_ADD 0x01
+#define TAC_PLUS_AUTHOR_STATUS_PASS_REPL 0x02
+#define TAC_PLUS_AUTHOR_STATUS_FAIL 0x10
+#define TAC_PLUS_AUTHOR_STATUS_ERROR 0x11
+#define TAC_PLUS_AUTHOR_STATUS_FOLLOW 0x21
+
+#define AUTHOR_STATUS_PASS_ADD TAC_PLUS_AUTHOR_STATUS_PASS_ADD
+#define AUTHOR_STATUS_PASS_REPL TAC_PLUS_AUTHOR_STATUS_PASS_REPL
+#define AUTHOR_STATUS_FAIL TAC_PLUS_AUTHOR_STATUS_FAIL
+#define AUTHOR_STATUS_ERROR TAC_PLUS_AUTHOR_STATUS_ERROR
+#define AUTHOR_STATUS_FOLLOW TAC_PLUS_AUTHOR_STATUS_FOLLOW
+
+};
+
+#define TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE 6
+
+#endif
diff --git a/libtac/lib/acct_r.c b/libtac/lib/acct_r.c
new file mode 100644
index 0000000..81eeb3c
--- /dev/null
+++ b/libtac/lib/acct_r.c
@@ -0,0 +1,190 @@
+/* acct_r.c - Read accounting reply from server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "xalloc.h"
+#include "libtac.h"
+#include "messages.h"
+
+/*
+ * return value:
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_READ_TIMEOUT
+ * LIBTAC_STATUS_SHORT_HDR
+ * LIBTAC_STATUS_SHORT_BODY
+ * LIBTAC_STATUS_PROTOCOL_ERR
+ * >= 0 : server response, see TAC_PLUS_AUTHEN_STATUS_...
+ */
+int tac_acct_read(int fd, struct areply *re) {
+ HDR th;
+ struct acct_reply *tb = NULL;
+ unsigned int len_from_header, len_from_body;
+ int r;
+ ssize_t packet_read;
+ char *msg = NULL;
+ int timeleft;
+ re->attr = NULL; /* unused */
+ re->msg = NULL;
+
+ if (tac_readtimeout_enable &&
+ tac_read_wait(fd,tac_timeout*1000, TAC_PLUS_HDR_SIZE,&timeleft) < 0 ) {
+ if (timeleft > 0) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply error, connection closed", __func__))
+ re->status = LIBTAC_STATUS_CONN_CLOSED;
+ }
+ else {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply timeout after %d secs", __func__, tac_timeout))
+ re->status = LIBTAC_STATUS_READ_TIMEOUT;
+ }
+ re->msg = tac_xstrdup(acct_syserr_msg);
+ free(tb);
+ return re->status;
+ }
+
+ packet_read = read(fd, &th, TAC_PLUS_HDR_SIZE);
+ if(packet_read < TAC_PLUS_HDR_SIZE) {
+ if (packet_read < 0)
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply header read error: %m", __func__))
+ else
+ TACSYSLOG((LOG_ERR,\
+ "%s: short reply header, read %ld of %d", __func__,\
+ packet_read, TAC_PLUS_HDR_SIZE))
+ re->msg = tac_xstrdup(acct_syserr_msg);
+ re->status = LIBTAC_STATUS_SHORT_HDR;
+ free(tb);
+ return re->status;
+ }
+
+ /* check the reply fields in header */
+ msg = _tac_check_header(&th, TAC_PLUS_ACCT);
+ if(msg != NULL) {
+ re->msg = tac_xstrdup(msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d, status message \"%s\"",\
+ __func__, re->status, re->msg != NULL ? re->msg : ""))
+ return re->status;
+ }
+
+ len_from_header=ntohl(th.datalength);
+ if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: length declared in the packet %d exceeds max packet size %d",\
+ __func__,\
+ len_from_header, TAC_PLUS_MAX_PACKET_SIZE))
+ re->status=LIBTAC_STATUS_SHORT_HDR;
+ free(tb);
+ return re->status;
+ }
+ tb=(struct acct_reply *) tac_xcalloc(1, len_from_header);
+
+ /* read reply packet body */
+ if (tac_readtimeout_enable &&
+ tac_read_wait(fd,timeleft,len_from_header,&timeleft) < 0 ) {
+ if (timeleft > 0) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply error, connection closed", __func__))
+ re->status = LIBTAC_STATUS_CONN_CLOSED;
+ }
+ else {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply timeout after %d secs", __func__, tac_timeout))
+ re->status = LIBTAC_STATUS_READ_TIMEOUT;
+ }
+ re->msg = tac_xstrdup(acct_syserr_msg);
+ free(tb);
+ return re->status;
+ }
+
+ r=read(fd, tb, len_from_header);
+ if(r < len_from_header) {
+ if (r < 0)
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply body read error: %m", __func__))
+ else
+ TACSYSLOG((LOG_ERR,\
+ "%s: short reply body, read %d of %d",\
+ __func__, r, len_from_header))
+ re->msg = tac_xstrdup(acct_syserr_msg);
+ re->status = LIBTAC_STATUS_SHORT_BODY;
+ free(tb);
+ return re->status;
+ }
+
+ /* decrypt the body */
+ _tac_crypt((u_char *) tb, &th, len_from_header);
+
+ /* Convert network byte order to host byte order */
+ tb->msg_len = ntohs(tb->msg_len);
+ tb->data_len = ntohs(tb->data_len);
+
+ /* check the length fields */
+ len_from_body=sizeof(tb->msg_len) + sizeof(tb->data_len) +
+ sizeof(tb->status) + tb->msg_len + tb->data_len;
+
+ if(len_from_header != len_from_body) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: inconsistent reply body, incorrect key?",\
+ __func__))
+ re->msg = tac_xstrdup(acct_syserr_msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+
+ /* save status and clean up */
+ r=tb->status;
+ if(tb->msg_len) {
+ msg=(char *) tac_xcalloc(1, tb->msg_len+1);
+ bcopy((u_char *) tb+TAC_ACCT_REPLY_FIXED_FIELDS_SIZE, msg, tb->msg_len);
+ msg[(int)tb->msg_len] = '\0';
+ re->msg = msg; /* Freed by caller */
+ }
+
+ /* server logged our request successfully */
+ if (tb->status == TAC_PLUS_ACCT_STATUS_SUCCESS) {
+ TACDEBUG((LOG_DEBUG, "%s: accounted ok", __func__))
+ if (!re->msg) re->msg = tac_xstrdup(acct_ok_msg);
+ re->status = tb->status;
+ free(tb);
+ return re->status;
+ }
+
+ TACDEBUG((LOG_DEBUG, "%s: accounting failed, server reply status=%d",\
+ __func__, r))
+ switch(tb->status) {
+ case TAC_PLUS_ACCT_STATUS_FOLLOW:
+ re->status = tb->status;
+ if (!re->msg) re->msg=tac_xstrdup(acct_fail_msg);
+ break;
+
+ case TAC_PLUS_ACCT_STATUS_ERROR:
+ default:
+ re->status = tb->status;
+ if (!re->msg) re->msg=tac_xstrdup(acct_err_msg);
+ break;
+ }
+
+ free(tb);
+ return re->status;
+}
diff --git a/libtac/lib/acct_s.c b/libtac/lib/acct_s.c
new file mode 100644
index 0000000..d131102
--- /dev/null
+++ b/libtac/lib/acct_s.c
@@ -0,0 +1,179 @@
+/* acct_s.c - Send accounting event information to server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "libtac.h"
+#include "xalloc.h"
+
+char *tac_acct_flag2str(int flag) {
+ switch(flag) {
+ case TAC_PLUS_ACCT_FLAG_MORE:
+ return "more";
+ case TAC_PLUS_ACCT_FLAG_START:
+ return "start";
+ case TAC_PLUS_ACCT_FLAG_STOP:
+ return "stop";
+ case TAC_PLUS_ACCT_FLAG_WATCHDOG:
+ return "update";
+ default:
+ return "unknown";
+ }
+}
+
+/*
+ * return value:
+ * 0 : success
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_WRITE_ERR
+ * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl)
+ * LIBTAC_STATUS_ASSEMBLY_ERR (pending impl)
+ */
+int tac_acct_send(int fd, int type, const char *user, char *tty,
+ char *r_addr, struct tac_attrib *attr) {
+
+ HDR *th;
+ struct acct tb;
+ u_char user_len, port_len, r_addr_len;
+ struct tac_attrib *a;
+ int i = 0; /* arg count */
+ int pkt_len = 0;
+ int pktl = 0;
+ int w; /* write count */
+ u_char *pkt=NULL;
+ /* u_char *pktp; */ /* obsolute */
+ int ret = 0;
+
+ th = _tac_req_header(TAC_PLUS_ACCT, 0);
+
+ /* set header options */
+ th->version=TAC_PLUS_VER_0;
+ th->encryption=tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG;
+
+ TACDEBUG((LOG_DEBUG, "%s: user '%s', tty '%s', rem_addr '%s', encrypt: %s, type: %s", \
+ __func__, user, tty, r_addr, \
+ (tac_encryption) ? "yes" : "no", \
+ tac_acct_flag2str(type)))
+
+ user_len=(u_char) strlen(user);
+ port_len=(u_char) strlen(tty);
+ r_addr_len=(u_char) strlen(r_addr);
+
+ tb.flags=(u_char) type;
+ tb.authen_method=tac_authen_method;
+ tb.priv_lvl=tac_priv_lvl;
+ if (!*tac_login) {
+ /* default to PAP */
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP;
+ } else {
+ if (strcmp(tac_login,"chap") == 0) {
+ tb.authen_type=TAC_PLUS_AUTHEN_TYPE_CHAP;
+ } else if(strcmp(tac_login,"login") == 0) {
+ tb.authen_type=TAC_PLUS_AUTHEN_TYPE_ASCII;
+ } else {
+ tb.authen_type=TAC_PLUS_AUTHEN_TYPE_PAP;
+ }
+ }
+ tb.authen_service=tac_authen_service;
+ tb.user_len=user_len;
+ tb.port_len=port_len;
+ tb.r_addr_len=r_addr_len;
+
+ /* allocate packet */
+ pkt=(u_char *) tac_xcalloc(1, TAC_ACCT_REQ_FIXED_FIELDS_SIZE);
+ pkt_len=sizeof(tb);
+
+ /* fill attribute length fields */
+ a = attr;
+ while (a) {
+ pktl = pkt_len;
+ pkt_len += sizeof(a->attr_len);
+ pkt = (u_char*) tac_xrealloc(pkt, pkt_len);
+
+ /* see comments in author_s.c
+ pktp=pkt + pkt_len;
+ pkt_len += sizeof(a->attr_len);
+ pkt = tac_xrealloc(pkt, pkt_len);
+ */
+
+ bcopy(&a->attr_len, pkt + pktl, sizeof(a->attr_len));
+ i++;
+
+ a = a->next;
+ }
+
+ /* fill the arg count field and add the fixed fields to packet */
+ tb.arg_cnt = i;
+ bcopy(&tb, pkt, TAC_ACCT_REQ_FIXED_FIELDS_SIZE);
+
+ /*
+#define PUTATTR(data, len) \
+ pktp = pkt + pkt_len; \
+ pkt_len += len; \
+ pkt = tac_xrealloc(pkt, pkt_len); \
+ bcopy(data, pktp, len);
+ */
+#define PUTATTR(data, len) \
+ pktl = pkt_len; \
+ pkt_len += len; \
+ pkt = (u_char*) tac_xrealloc(pkt, pkt_len); \
+ bcopy(data, pkt + pktl, len);
+
+ /* fill user and port fields */
+ PUTATTR(user, user_len)
+ PUTATTR(tty, port_len)
+ PUTATTR(r_addr, r_addr_len)
+
+ /* fill attributes */
+ a = attr;
+ while(a) {
+ PUTATTR(a->attr, a->attr_len)
+ a = a->next;
+ }
+
+ /* finished building packet, fill len_from_header in header */
+ th->datalength = htonl(pkt_len);
+
+ /* write header */
+ w = write(fd, th, TAC_PLUS_HDR_SIZE);
+
+ if(w < TAC_PLUS_HDR_SIZE) {
+ TACSYSLOG((LOG_ERR, "%s: short write on header, wrote %d of %d: %m",\
+ __func__, w, TAC_PLUS_HDR_SIZE))
+ free(pkt);
+ free(th);
+ return LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ /* encrypt packet body */
+ _tac_crypt(pkt, th, pkt_len);
+
+ /* write body */
+ w=write(fd, pkt, pkt_len);
+ if(w < pkt_len) {
+ TACSYSLOG((LOG_ERR, "%s: short write on body, wrote %d of %d: %m",\
+ __func__, w, pkt_len))
+ ret = LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ free(pkt);
+ free(th);
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d", __func__, ret))
+ return ret;
+}
diff --git a/libtac/lib/attrib.c b/libtac/lib/attrib.c
new file mode 100644
index 0000000..31ffd56
--- /dev/null
+++ b/libtac/lib/attrib.c
@@ -0,0 +1,99 @@
+/* attrib.c - Procedures for handling internal list of attributes
+ * for accounting and authorization functions.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "libtac.h"
+#include "xalloc.h"
+
+void tac_add_attrib(struct tac_attrib **attr, char *name, char *value) {
+ tac_add_attrib_pair(attr, name, '=', value);
+}
+
+void tac_add_attrib_pair(struct tac_attrib **attr, char *name, char sep, char *value) {
+ struct tac_attrib *a;
+ u_char l1 = (u_char) strlen(name);
+ u_char l2;
+ int total_len;
+
+ if (value == NULL) {
+ l2 = 0;
+ } else {
+ l2 = (u_char) strlen(value);
+ }
+ total_len = l1 + l2 + 1; /* "name" + "=" + "value" */
+
+ if (total_len > 255) {
+ TACSYSLOG((LOG_WARNING,\
+ "%s: attribute `%s' total length exceeds 255 characters, skipping",\
+ __func__, name))
+ return;
+ }
+
+ /* initialize the list if application passed us a null pointer */
+ if(*attr == NULL) {
+ *attr = (struct tac_attrib *) tac_xcalloc(1, sizeof(struct tac_attrib));
+ a = *attr;
+ } else {
+ /* find the last allocated block */
+ a = *attr;
+ while(a->next != NULL)
+ a = a->next; /* a holds last allocated block */
+
+ a->next = (struct tac_attrib *) tac_xcalloc(1,
+ sizeof(struct tac_attrib));
+ a = a->next; /* set current block pointer to the new one */
+ }
+
+ if ( sep != '=' && sep != '*' ) {
+ sep = '=';
+ }
+
+ /* fill the block */
+ a->attr_len=total_len;
+ a->attr = (char *) tac_xcalloc(1, total_len+1);
+ bcopy(name, a->attr, l1); /* paste name */
+ *(a->attr+l1)=sep; /* insert seperator "[=*]" */
+ if (value != NULL) {
+ bcopy(value, (a->attr+l1+1), l2); /* paste value */
+ }
+ *(a->attr+total_len) = '\0'; /* add 0 for safety */
+ a->next = NULL; /* make sure it's null */
+}
+
+void tac_free_attrib(struct tac_attrib **attr) {
+ struct tac_attrib *a;
+ struct tac_attrib *b;
+
+ if(*attr == NULL)
+ return;
+
+ b = *attr;
+ *attr = NULL;
+
+ /* find last allocated block */
+ do {
+ a = b;
+ b = a->next;
+ free(a->attr);
+ free(a);
+ } while (b != NULL);
+
+}
diff --git a/libtac/lib/authen_r.c b/libtac/lib/authen_r.c
new file mode 100644
index 0000000..e520420
--- /dev/null
+++ b/libtac/lib/authen_r.c
@@ -0,0 +1,197 @@
+/* authen_r.c - Read authentication reply from server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "xalloc.h"
+#include "libtac.h"
+#include "messages.h"
+
+/* reads packet from TACACS+ server; returns:
+ * TAC_PLUS_AUTHEN_STATUS_PASS if the authentication succeded
+ * an other integer if failed. Check tacplus.h for all possible values
+ *
+ * return value:
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_READ_TIMEOUT
+ * LIBTAC_STATUS_SHORT_HDR
+ * LIBTAC_STATUS_SHORT_BODY
+ * LIBTAC_STATUS_PROTOCOL_ERR
+ * >= 0 : server response, see TAC_PLUS_AUTHEN_STATUS_...
+ */
+int tac_authen_read(int fd, struct areply *re) {
+ HDR th;
+ struct authen_reply *tb = NULL;
+ int r;
+ unsigned int len_from_header, len_from_body;
+ ssize_t packet_read;
+ char *msg = NULL;
+ int timeleft;
+
+ memset(re, 0, sizeof(struct areply));
+
+ /* read the reply header */
+ if (tac_readtimeout_enable &&
+ tac_read_wait(fd,tac_timeout*1000,TAC_PLUS_HDR_SIZE,&timeleft) < 0 ) {
+ if (timeleft > 0) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply error, connection closed", __func__))
+ re->status = LIBTAC_STATUS_CONN_CLOSED;
+ }
+ else {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply timeout after %d secs", __func__, tac_timeout))
+ re->status=LIBTAC_STATUS_READ_TIMEOUT;
+ }
+ free(tb);
+ return re->status;
+ }
+ packet_read = read(fd, &th, TAC_PLUS_HDR_SIZE);
+ if (packet_read < TAC_PLUS_HDR_SIZE) {
+ if (packet_read < 0)
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply header read error: %m", __func__))
+ else
+ TACSYSLOG((LOG_ERR,\
+ "%s: short reply header, read %d of %d",\
+ __func__, r, TAC_PLUS_HDR_SIZE))
+ re->status=LIBTAC_STATUS_READ_TIMEOUT;
+ free(tb);
+ return re->status;
+ }
+
+ /* check the reply fields in header */
+ msg = _tac_check_header(&th, TAC_PLUS_AUTHEN);
+ if(msg != NULL) {
+ re->msg = tac_xstrdup(msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+
+ re->seq_no = th.seq_no;
+
+ len_from_header = ntohl(th.datalength);
+ if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: length declared in the packet %d exceeds max packet size %d",\
+ __func__,\
+ len_from_header, TAC_PLUS_MAX_PACKET_SIZE))
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+ tb = (struct authen_reply *) tac_xcalloc(1, len_from_header);
+
+ /* read reply packet body */
+ if (tac_readtimeout_enable &&
+ tac_read_wait(fd,timeleft,len_from_header,&timeleft) < 0 ) {
+ if (timeleft > 0) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply error, connection closed", __func__))
+ re->status = LIBTAC_STATUS_CONN_CLOSED;
+ }
+ else {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply timeout after %d secs", __func__, tac_timeout))
+ re->status=LIBTAC_STATUS_READ_TIMEOUT;
+ }
+ re->msg = tac_xstrdup(authen_syserr_msg);
+ free(tb);
+ return re->status;
+ }
+ r = read(fd, tb, len_from_header);
+ if (r < len_from_header) {
+ if (r < 0)
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply body read failed: %m", __func__))
+ else
+ TACSYSLOG((LOG_ERR,\
+ "%s: short reply body, read %d of %d",\
+ __func__, r, len_from_header))
+ re->msg = tac_xstrdup(authen_syserr_msg);
+ re->status = LIBTAC_STATUS_SHORT_BODY;
+ free(tb);
+ return re->status;
+ }
+
+ /* decrypt the body */
+ _tac_crypt((u_char *) tb, &th, len_from_header);
+
+ /* Convert network byte order to host byte order */
+ tb->msg_len = ntohs(tb->msg_len);
+ tb->data_len = ntohs(tb->data_len);
+
+ /* check the length fields */
+ len_from_body = sizeof(tb->status) + sizeof(tb->flags) +
+ sizeof(tb->msg_len) + sizeof(tb->data_len) +
+ tb->msg_len + tb->data_len;
+
+ if(len_from_header != len_from_body) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: inconsistent reply body, incorrect key?",\
+ __func__))
+ re->msg = tac_xstrdup(protocol_err_msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+
+ /* save status and clean up */
+ re->status = r = tb->status;
+
+ if (0 < tb->msg_len) {
+ msg = tac_xcalloc(tb->msg_len + 1, sizeof(char));
+ memset(msg, 0, (tb->msg_len + 1));
+ memcpy(msg, (char*)tb + sizeof(struct authen_reply), tb->msg_len);
+
+ re->msg = msg;
+ }
+
+ /* server authenticated username and password successfully */
+ if (r == TAC_PLUS_AUTHEN_STATUS_PASS) {
+ TACDEBUG((LOG_DEBUG, "%s: authentication ok", __func__))
+ free(tb);
+ return re->status;
+ }
+
+ /* server ask for continue packet with password */
+ if (r == TAC_PLUS_AUTHEN_STATUS_GETPASS) {
+ TACDEBUG((LOG_DEBUG, "%s: continue packet with password needed", __func__))
+ free(tb);
+ return re->status;
+ }
+
+ /* server wants to prompt user for more data */
+ if (r == TAC_PLUS_AUTHEN_STATUS_GETDATA) {
+ re->flags = tb->flags;
+
+ TACDEBUG((LOG_DEBUG, "%s: continue packet with data request: msg=%.*s",
+ __func__, tb->msg_len, (char*)tb + sizeof(struct authen_reply)))
+ free(tb);
+ return re->status;
+ }
+
+ TACDEBUG((LOG_DEBUG, "%s: authentication failed, server reply status=%d",
+ __func__, r))
+
+ free(tb);
+ return re->status;
+} /* tac_authen_read */
+
diff --git a/libtac/lib/authen_s.c b/libtac/lib/authen_s.c
new file mode 100644
index 0000000..62c5566
--- /dev/null
+++ b/libtac/lib/authen_s.c
@@ -0,0 +1,190 @@
+/* authen_s.c - Send authentication request to the server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#include "libtac.h"
+#include "xalloc.h"
+
+#if defined(HAVE_OPENSSL_MD5_H) && defined(HAVE_LIBCRYPTO)
+# include <openssl/md5.h>
+#ifndef MD5_LEN
+# define MD5_LEN MD5_LBLOCK
+#endif
+#else
+# include "md5.h"
+#endif
+
+/* this function sends a packet do TACACS+ server, asking
+ * for validation of given username and password
+ *
+ * return value:
+ * 0 : success
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_WRITE_ERR
+ * LIBTAC_STATUS_WRITE_TIMEOUT
+ * LIBTAC_STATUS_ASSEMBLY_ERR
+ */
+int tac_authen_send(int fd, const char *user, char *pass, char *tty,
+ char *r_addr, u_char action) {
+
+ HDR *th; /* TACACS+ packet header */
+ struct authen_start tb; /* message body */
+ int user_len, port_len, chal_len, mdp_len, token_len, bodylength, w;
+ int r_addr_len;
+ int pkt_len = 0;
+ int ret = 0;
+ char *chal = "1234123412341234";
+ char digest[MD5_LEN];
+ char *token = NULL;
+ u_char *pkt = NULL, *mdp = NULL;
+ MD5_CTX mdcontext;
+
+ th=_tac_req_header(TAC_PLUS_AUTHEN, 0);
+
+ /* set some header options */
+ if (!strcmp(tac_login,"login")) {
+ th->version = TAC_PLUS_VER_0;
+ } else {
+ th->version = TAC_PLUS_VER_1;
+ }
+ th->encryption = tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG;
+
+ TACDEBUG((LOG_DEBUG, "%s: user '%s', tty '%s', rem_addr '%s', encrypt: %s", \
+ __func__, user, tty, r_addr, \
+ (tac_encryption) ? "yes" : "no"))
+
+ if (!strcmp(tac_login,"chap")) {
+ chal_len = strlen(chal);
+ mdp_len = sizeof(u_char) + strlen(pass) + chal_len;
+ mdp = (u_char *) tac_xcalloc(1, mdp_len);
+ mdp[0] = 5;
+ memcpy(&mdp[1], pass, strlen(pass));
+ memcpy(mdp + strlen(pass) + 1, chal, chal_len);
+#if defined(HAVE_OPENSSL_MD5_H) && defined(HAVE_LIBCRYPTO)
+ MD5_Init(&mdcontext);
+ MD5_Update(&mdcontext, mdp, mdp_len);
+ MD5_Final((u_char *) digest, &mdcontext);
+#else
+ MD5Init(&mdcontext);
+ MD5Update(&mdcontext, mdp, mdp_len);
+ MD5Final((u_char *) digest, &mdcontext);
+#endif
+ free(mdp);
+ token = (char*) tac_xcalloc(1, sizeof(u_char) + 1 + chal_len + MD5_LEN);
+ token[0] = 5;
+ memcpy(&token[1], chal, chal_len);
+ memcpy(token + chal_len + 1, digest, MD5_LEN);
+ } else {
+ token = tac_xstrdup(pass);
+ }
+
+ /* get size of submitted data */
+ user_len = strlen(user);
+ port_len = strlen(tty);
+ r_addr_len = strlen(r_addr);
+ token_len = strlen(token);
+
+ /* fill the body of message */
+ tb.action = action;
+ tb.priv_lvl = tac_priv_lvl;
+ if (!*tac_login) {
+ /* default to PAP */
+ tb.authen_type = TAC_PLUS_AUTHEN_CHPASS == action ? TAC_PLUS_AUTHEN_TYPE_ASCII : TAC_PLUS_AUTHEN_TYPE_PAP;
+ } else {
+ if (!strcmp(tac_login,"chap")) {
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_CHAP;
+ } else if (!strcmp(tac_login,"login")) {
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_ASCII;
+ } else {
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP;
+ }
+ }
+ tb.service = tac_authen_service;
+ tb.user_len = user_len;
+ tb.port_len = port_len;
+ tb.r_addr_len = r_addr_len; /* may be e.g Caller-ID in future */
+ tb.data_len = token_len;
+
+ /* fill body length in header */
+ bodylength = sizeof(tb) + user_len
+ + port_len + r_addr_len + token_len;
+
+ th->datalength = htonl(bodylength);
+
+ /* we can now write the header */
+ w = write(fd, th, TAC_PLUS_HDR_SIZE);
+ if (w < 0 || w < TAC_PLUS_HDR_SIZE) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: short write on header, wrote %d of %d: %m",\
+ __func__, w, TAC_PLUS_HDR_SIZE))
+ free(token);
+ free(pkt);
+ free(th);
+ return LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ /* build the packet */
+ pkt = (u_char *) tac_xcalloc(1, bodylength+10);
+
+ bcopy(&tb, pkt+pkt_len, sizeof(tb)); /* packet body beginning */
+ pkt_len += sizeof(tb);
+ bcopy(user, pkt+pkt_len, user_len); /* user */
+ pkt_len += user_len;
+ bcopy(tty, pkt+pkt_len, port_len); /* tty */
+ pkt_len += port_len;
+ bcopy(r_addr, pkt+pkt_len, r_addr_len); /* rem addr */
+ pkt_len += r_addr_len;
+
+ bcopy(token, pkt+pkt_len, token_len); /* password */
+ pkt_len += token_len;
+
+ /* pkt_len == bodylength ? */
+ if (pkt_len != bodylength) {
+ TACSYSLOG((LOG_ERR, "%s: bodylength %d != pkt_len %d",\
+ __func__, bodylength, pkt_len))
+ free(token);
+ free(pkt);
+ free(th);
+ return LIBTAC_STATUS_ASSEMBLY_ERR;
+ }
+
+ /* encrypt the body */
+ _tac_crypt(pkt, th, bodylength);
+
+ w = write(fd, pkt, pkt_len);
+ if (w < 0 || w < pkt_len) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: short write on body, wrote %d of %d: %m",\
+ __func__, w, pkt_len))
+ ret = LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ free(token);
+ free(pkt);
+ free(th);
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d", __func__, ret))
+ return ret;
+} /* tac_authen_send */
+
+
diff --git a/libtac/lib/author_r.c b/libtac/lib/author_r.c
new file mode 100644
index 0000000..ba73056
--- /dev/null
+++ b/libtac/lib/author_r.c
@@ -0,0 +1,283 @@
+/* author_r.c - Reads authorization reply from the server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "xalloc.h"
+#include "libtac.h"
+#include "messages.h"
+
+/* This function returns structure containing
+ 1. status (granted/denied)
+ 2. message for the user
+ 3. list of attributes returned by server
+ The attributes should be applied to service authorization
+ is requested for.
+ *
+ * return value:
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_READ_TIMEOUT
+ * LIBTAC_STATUS_SHORT_HDR
+ * LIBTAC_STATUS_SHORT_BODY
+ * LIBTAC_STATUS_PROTOCOL_ERR
+ * >= 0 : server response, see TAC_PLUS_AUTHOR_STATUS_...
+ */
+int tac_author_read(int fd, struct areply *re) {
+ HDR th;
+ struct author_reply *tb = NULL;
+ unsigned int len_from_header, len_from_body;
+ int r;
+ ssize_t packet_read;
+ u_char *pktp = NULL;
+ char *msg = NULL;
+ int timeleft;
+ re->msg = NULL;
+
+ bzero(re, sizeof(struct areply));
+ if (tac_readtimeout_enable &&
+ tac_read_wait(fd,tac_timeout*1000,TAC_PLUS_HDR_SIZE,&timeleft) < 0 ) {
+ if (timeleft > 0) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply error, connection closed", __func__))
+ re->status = LIBTAC_STATUS_CONN_CLOSED;
+ }
+ else {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply timeout after %d secs", __func__, tac_timeout))
+ re->status = LIBTAC_STATUS_READ_TIMEOUT;
+ }
+ re->msg = tac_xstrdup(author_syserr_msg);
+ free(tb);
+ return re->status;
+ }
+
+ packet_read = read(fd, &th, TAC_PLUS_HDR_SIZE);
+ if(packet_read < TAC_PLUS_HDR_SIZE) {
+ if (packet_read < 0)
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply header read error: %m", __func__))
+ else
+ TACSYSLOG((LOG_ERR,\
+ "%s: short reply header, read %ld of %d", __func__,\
+ packet_read, TAC_PLUS_HDR_SIZE))
+ re->msg = tac_xstrdup(author_syserr_msg);
+ re->status = LIBTAC_STATUS_SHORT_HDR;
+ free(tb);
+ return re->status;
+ }
+
+ /* check header consistency */
+ msg = _tac_check_header(&th, TAC_PLUS_AUTHOR);
+ if (msg != NULL) {
+ /* no need to process body if header is broken */
+ re->msg = tac_xstrdup(msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+
+ len_from_header = ntohl(th.datalength);
+ if (len_from_header > TAC_PLUS_MAX_PACKET_SIZE) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: length declared in the packet %d exceeds max packet size %d",\
+ __func__,\
+ len_from_header, TAC_PLUS_MAX_PACKET_SIZE))
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+ tb = (struct author_reply *) tac_xcalloc(1, len_from_header);
+
+ /* read reply packet body */
+ if (tac_readtimeout_enable &&
+ tac_read_wait(fd,timeleft,len_from_header,&timeleft) < 0 ) {
+ if (timeleft > 0) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply error, connection closed", __func__))
+ re->status = LIBTAC_STATUS_CONN_CLOSED;
+ }
+ else {
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply timeout after %d secs", __func__, tac_timeout))
+ re->status = LIBTAC_STATUS_READ_TIMEOUT;
+ }
+ re->msg = tac_xstrdup(author_syserr_msg);
+ free(tb);
+ return re->status;
+ }
+ packet_read = read(fd, tb, len_from_header);
+ if (packet_read < len_from_header) {
+ if (packet_read < 0)
+ TACSYSLOG((LOG_ERR,\
+ "%s: reply body read error: %m", __func__))
+ else
+ TACSYSLOG((LOG_ERR,\
+ "%s: short reply body, read %ld of %d", __func__,\
+ packet_read, len_from_header))
+ re->msg = tac_xstrdup(author_syserr_msg);
+ re->status = LIBTAC_STATUS_SHORT_BODY;
+ free(tb);
+ return re->status;
+ }
+
+ /* decrypt the body */
+ _tac_crypt((u_char *) tb, &th, len_from_header);
+
+ /* Convert network byte order to host byte order */
+ tb->msg_len = ntohs(tb->msg_len);
+ tb->data_len = ntohs(tb->data_len);
+
+ /* check consistency of the reply body
+ * len_from_header = declared in header
+ * len_from_body = value computed from body fields
+ */
+ len_from_body = TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE +
+ tb->msg_len + tb->data_len;
+
+ pktp = (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE;
+
+ /* cycle through the arguments supplied in the packet */
+ for (r = 0; r < tb->arg_cnt; r++) {
+ if (len_from_body > packet_read || ((void *)pktp - (void *) tb) > packet_read) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: arguments supplied in packet seem to exceed its size",\
+ __func__))
+ re->msg = tac_xstrdup(protocol_err_msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+ len_from_body += sizeof(u_char); /* add arg length field's size*/
+ len_from_body += *pktp; /* add arg length itself */
+ pktp++;
+ }
+
+ if(len_from_header != len_from_body) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: inconsistent reply body, incorrect key?",\
+ __func__))
+ re->msg = tac_xstrdup(protocol_err_msg);
+ re->status = LIBTAC_STATUS_PROTOCOL_ERR;
+ free(tb);
+ return re->status;
+ }
+
+ /* packet seems to be consistent, prepare return messages */
+ /* server message for user */
+ if(tb->msg_len) {
+ char *msg = (char *) tac_xcalloc(1, tb->msg_len+1);
+ bcopy((u_char *) tb+TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE
+ + (tb->arg_cnt)*sizeof(u_char),
+ msg, tb->msg_len);
+ msg[(int) tb->msg_len] = '\0';
+ re->msg = msg; /* freed by caller */
+ }
+
+ /* server message to syslog */
+ if(tb->data_len) {
+ char *smsg=(char *) tac_xcalloc(1, tb->data_len+1);
+ bcopy((u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE
+ + (tb->arg_cnt)*sizeof(u_char)
+ + tb->msg_len, smsg,
+ tb->data_len);
+ smsg[(int) tb->data_len] = '\0';
+ TACSYSLOG((LOG_ERR, "%s: reply message: %s", __func__,smsg))
+ free(smsg);
+ }
+
+ TACDEBUG((LOG_DEBUG, "%s: authorization reply status=%d",\
+ __func__, tb->status));
+
+ /* prepare status */
+ switch(tb->status) {
+ /* success conditions */
+ /* XXX support optional vs mandatory arguments */
+ case TAC_PLUS_AUTHOR_STATUS_PASS_REPL:
+ tac_free_attrib(&re->attr);
+
+ case TAC_PLUS_AUTHOR_STATUS_PASS_ADD:
+ {
+ u_char *argp;
+
+ if(!re->msg) re->msg=tac_xstrdup(author_ok_msg);
+ re->status=tb->status;
+
+ /* add attributes received to attribute list returned to
+ the client */
+ pktp = (u_char *) tb + TAC_AUTHOR_REPLY_FIXED_FIELDS_SIZE;
+ argp = pktp + (tb->arg_cnt * sizeof(u_char)) + tb->msg_len +
+ tb->data_len;
+ TACDEBUG((LOG_DEBUG, "Args cnt %d", tb->arg_cnt));
+ /* argp points to current argument string
+ pktp points to current argument length */
+ for(r=0; r < tb->arg_cnt; r++) {
+ char buff[256];
+ char *sep;
+ char *value;
+ char sepchar = '=';
+
+ bcopy(argp, buff, (int)*pktp);
+ buff[(int)*pktp] = '\0';
+ sep = strchr(buff, '=');
+ if ( sep == NULL ) {
+ sep = strchr(buff, '*');
+ }
+ if(sep == NULL) {
+ TACSYSLOG((LOG_WARNING,\
+ "AUTHOR_STATUS_PASS_ADD/REPL: av pair does not contain a separator: %s",\
+ buff))
+ /* now buff points to attribute name, make value ""
+ treat as "name=" */
+ value = "";
+ } else {
+ sepchar = *sep;
+ *sep = '\0';
+ value = ++sep;
+ /* now buff points to attribute name,
+ value to the attribute value */
+ }
+ TACDEBUG((LOG_DEBUG, "Adding buf/value pair (%s,%s)", buff, value));
+ tac_add_attrib_pair(&re->attr, buff, sepchar, value);
+ argp += *pktp;
+ pktp++;
+ }
+ }
+ free(tb);
+ return re->status;
+ break;
+ }
+
+ switch (tb->status) {
+ /* authorization failure conditions */
+ /* failing to follow is allowed by RFC, page 23 */
+ case TAC_PLUS_AUTHOR_STATUS_FOLLOW:
+ case TAC_PLUS_AUTHOR_STATUS_FAIL:
+ if(!re->msg) re->msg = tac_xstrdup(author_fail_msg);
+ re->status=TAC_PLUS_AUTHOR_STATUS_FAIL;
+ break;
+ /* error conditions */
+ case TAC_PLUS_AUTHOR_STATUS_ERROR:
+ default:
+ if(!re->msg) re->msg = tac_xstrdup(author_err_msg);
+ re->status=TAC_PLUS_AUTHOR_STATUS_ERROR;
+ }
+
+ free(tb);
+ return re->status;
+}
diff --git a/libtac/lib/author_s.c b/libtac/lib/author_s.c
new file mode 100644
index 0000000..cc4622a
--- /dev/null
+++ b/libtac/lib/author_s.c
@@ -0,0 +1,154 @@
+/* author_s.c - Send authorization request to the server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "libtac.h"
+#include "xalloc.h"
+
+/* Send authorization request to the server, along with attributes
+ specified in attribute list prepared with tac_add_attrib.
+ *
+ * return value:
+ * 0 : success
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_WRITE_ERR
+ * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl)
+ * LIBTAC_STATUS_ASSEMBLY_ERR (pending impl)
+ */
+int tac_author_send(int fd, const char *user, char *tty, char *r_addr,
+ struct tac_attrib *attr) {
+
+ HDR *th;
+ struct author tb;
+ u_char user_len, port_len, r_addr_len;
+ struct tac_attrib *a;
+ int i = 0; /* attributes count */
+ int pkt_len = 0; /* current packet length */
+ int pktl = 0; /* temporary storage for previous pkt_len values */
+ int w; /* write() return value */
+ u_char *pkt = NULL; /* packet building pointer */
+ /* u_char *pktp; */ /* obsolete */
+ int ret = 0;
+
+ th=_tac_req_header(TAC_PLUS_AUTHOR, 0);
+
+ /* set header options */
+ th->version=TAC_PLUS_VER_0;
+ th->encryption=tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG;
+
+ TACDEBUG((LOG_DEBUG, "%s: user '%s', tty '%s', rem_addr '%s', encrypt: %s", \
+ __func__, user, \
+ tty, r_addr, tac_encryption ? "yes" : "no"))
+
+ user_len = (u_char) strlen(user);
+ port_len = (u_char) strlen(tty);
+ r_addr_len = (u_char) strlen(r_addr);
+
+ tb.authen_method = tac_authen_method;
+ tb.priv_lvl = tac_priv_lvl;
+ if (!*tac_login) {
+ /* default to PAP */
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP;
+ } else {
+ if (strcmp(tac_login,"chap") == 0) {
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_CHAP;
+ } else if (strcmp(tac_login,"login") == 0) {
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_ASCII;
+ } else {
+ tb.authen_type = TAC_PLUS_AUTHEN_TYPE_PAP;
+ }
+ }
+ tb.service = tac_authen_service;
+ tb.user_len = user_len;
+ tb.port_len = port_len;
+ tb.r_addr_len = r_addr_len;
+
+ /* allocate packet */
+ pkt = (u_char *) tac_xcalloc(1, TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE);
+ pkt_len = sizeof(tb);
+
+ /* fill attribute length fields */
+ a = attr;
+ while (a) {
+ pktl = pkt_len;
+ pkt_len += sizeof(a->attr_len);
+ pkt = (u_char*) tac_xrealloc(pkt, pkt_len);
+
+ bcopy(&a->attr_len, pkt + pktl, sizeof(a->attr_len));
+ i++;
+
+ a = a->next;
+ }
+
+ /* fill the arg count field and add the fixed fields to packet */
+ tb.arg_cnt = i;
+ bcopy(&tb, pkt, TAC_AUTHOR_REQ_FIXED_FIELDS_SIZE);
+
+#define PUTATTR(data, len) \
+ pktl = pkt_len; \
+ pkt_len += len; \
+ pkt = (u_char*) tac_xrealloc(pkt, pkt_len); \
+ bcopy(data, pkt + pktl, len);
+
+ /* fill user and port fields */
+ PUTATTR(user, user_len)
+ PUTATTR(tty, port_len)
+ PUTATTR(r_addr, r_addr_len)
+
+ /* fill attributes */
+ a = attr;
+ while (a) {
+ PUTATTR(a->attr, a->attr_len)
+
+ a = a->next;
+ }
+
+ /* finished building packet, fill len_from_header in header */
+ th->datalength = htonl(pkt_len);
+
+ /* write header */
+ w = write(fd, th, TAC_PLUS_HDR_SIZE);
+
+ if (w < TAC_PLUS_HDR_SIZE) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: short write on header, wrote %d of %d: %m",\
+ __func__, w, TAC_PLUS_HDR_SIZE))
+ free(pkt);
+ free(th);
+ return LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ /* encrypt packet body */
+ _tac_crypt(pkt, th, pkt_len);
+
+ /* write body */
+ w = write(fd, pkt, pkt_len);
+ if (w < pkt_len) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: short write on body, wrote %d of %d: %m",\
+ __func__, w, pkt_len))
+ ret = LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ free(pkt);
+ free(th);
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d", __func__, ret))
+ return ret;
+}
diff --git a/libtac/lib/connect.c b/libtac/lib/connect.c
new file mode 100644
index 0000000..8641ae2
--- /dev/null
+++ b/libtac/lib/connect.c
@@ -0,0 +1,236 @@
+/* connect.c - Open connection to server.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include <signal.h>
+#include <arpa/inet.h>
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef _AIX
+#include <sys/socket.h>
+#endif
+
+#include "libtac.h"
+
+/* Pointer to TACACS+ connection timeout */
+int tac_timeout = 5;
+
+/* Returns file descriptor of open connection
+ to the first available server from list passed
+ in server table.
+
+ * return value:
+ * >= 0 : valid fd
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * iface is used to bind to an interface or VRF if non-null
+ */
+int tac_connect(struct addrinfo **server, char **key, int servers, char *iface) {
+ int tries;
+ int fd=-1;
+
+ if(servers == 0 || server == NULL) {
+ TACSYSLOG((LOG_ERR, "%s: no TACACS+ servers defined", __func__))
+ } else {
+ for ( tries = 0; tries < servers; tries++ ) {
+ if((fd=tac_connect_single(server[tries], key[tries], NULL, iface)) >= 0 ) {
+ /* tac_secret was set in tac_connect_single on success */
+ break;
+ }
+ }
+ }
+
+ /* all attempts failed if fd is still < 0 */
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d",__func__, fd))
+ return fd;
+} /* tac_connect */
+
+
+/* return value:
+ * >= 0 : valid fd
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * If iface is non-null, try to setsockopt SO_BINDTODEVICE to that iface
+ * to support specific routing, including VRF.
+ * if srcaddr is non-null, try to bind() to that address to support
+ * specifying source IP addres
+ */
+int tac_connect_single(struct addrinfo *server, const char *key,
+ struct addrinfo *srcaddr, char *iface) {
+ int retval = LIBTAC_STATUS_CONN_ERR; /* default retval */
+ int fd = -1;
+ int flags, rc;
+ fd_set readfds, writefds;
+ struct timeval tv;
+ socklen_t len;
+ struct sockaddr_storage addr;
+ char *ip;
+
+ if(server == NULL) {
+ TACSYSLOG((LOG_ERR, "%s: no TACACS+ server defined", __func__))
+ return LIBTAC_STATUS_CONN_ERR;
+ }
+
+ /* format server address into a string for use in messages */
+ ip = tac_ntop(server->ai_addr);
+
+ if((fd=socket(server->ai_family, server->ai_socktype, server->ai_protocol)) < 0) {
+ TACSYSLOG((LOG_ERR,"%s: socket creation error: %s", __func__,
+ strerror(errno)))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+
+ if (iface) {
+ /* do not fail if the bind fails, connection may still succeed */
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface,
+ strlen(iface)+1) < 0)
+ TACSYSLOG((LOG_WARNING, "%s: Binding socket to device %s failed: %m",
+ __func__, iface))
+ }
+
+ /* get flags for restoration later */
+ flags = fcntl(fd, F_GETFL, 0);
+
+ /* put socket in non blocking mode for timeout support */
+ if( fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 ) {
+ TACSYSLOG((LOG_ERR, "%s: cannot set socket non blocking",\
+ __func__))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+
+ /* bind if source address got explicity defined */
+ if (srcaddr) {
+ if (bind(fd, srcaddr->ai_addr, srcaddr->ai_addrlen) < 0) {
+ TACSYSLOG((LOG_ERR, "%s: Failed to bind source address: %s",
+ __func__, strerror(errno)))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+ }
+
+ rc = connect(fd, server->ai_addr, server->ai_addrlen);
+ /* FIX this..for some reason errno = 0 on AIX... */
+ if((rc == -1) && (errno != EINPROGRESS) && (errno != 0)) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: connection to %s failed: %m", __func__, ip))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+
+ /* set fds for select */
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_SET(fd, &readfds);
+ FD_SET(fd, &writefds);
+
+ /* set timeout seconds */
+ tv.tv_sec = tac_timeout;
+ tv.tv_usec = 0;
+
+ /* check if socket is ready for read and write */
+ rc = select(fd+1, &readfds, &writefds, NULL, &tv);
+
+ /* timeout */
+ if ( rc == 0 ) {
+ retval = LIBTAC_STATUS_CONN_TIMEOUT;
+ goto error;
+ }
+
+ /* some other error or interrupt before timeout */
+ if ( rc < 0 ) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: connection failed with %s: %m", __func__, ip))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+
+ /* check with getpeername if we have a valid connection */
+ len = sizeof addr;
+ if(getpeername(fd, (struct sockaddr*)&addr, &len) == -1) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: getpeername failed with %s: %m", __func__, ip))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+
+ /* restore flags on socket */
+ if (flags & O_NONBLOCK) {
+ flags &= ~O_NONBLOCK;
+ }
+ if(fcntl(fd, F_SETFL, flags) == -1) {
+ TACSYSLOG((LOG_ERR, "%s: cannot restore socket flags: %m",\
+ __func__))
+ retval = LIBTAC_STATUS_CONN_ERR;
+ goto error;
+ }
+
+ /* connected ok */
+ TACDEBUG((LOG_DEBUG, "%s: connected to %s", __func__, ip))
+ retval = fd;
+
+ /* set current tac_secret */
+ tac_encryption = 0;
+ if (key != NULL && *key) {
+ tac_encryption = 1;
+ tac_secret = key;
+ }
+
+error:
+ if (retval < 0 && fd >= 0) /* we had an error, don't leak fd */
+ close(fd);
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d (fd=%d)",\
+ __func__, retval < 0 ? retval:0, fd))
+ return retval;
+} /* tac_connect_single */
+
+
+/* return value:
+ * ptr to char* with format IP address
+ * warning: returns a static buffer
+ * (which some ppl don't like, but it's robust and at last no more memory leaks)
+ */
+char *tac_ntop(const struct sockaddr *sa) {
+ static char server_address[INET6_ADDRSTRLEN+16];
+
+ switch(sa->sa_family) {
+ case AF_INET:
+ inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
+ server_address, INET_ADDRSTRLEN);
+
+ snprintf(server_address + strlen(server_address), 14, ":%hu",
+ htons(((struct sockaddr_in *)sa)->sin_port));
+ break;
+
+ case AF_INET6:
+ inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
+ server_address, INET6_ADDRSTRLEN);
+
+ snprintf(server_address + strlen(server_address), 14, ":%hu",
+ htons(((struct sockaddr_in6 *)sa)->sin6_port));
+ break;
+
+ default:
+ strcpy(server_address, "Unknown AF");
+ }
+ return server_address;
+} /* tac_ntop */
+
diff --git a/libtac/lib/cont_s.c b/libtac/lib/cont_s.c
new file mode 100644
index 0000000..e838d95
--- /dev/null
+++ b/libtac/lib/cont_s.c
@@ -0,0 +1,104 @@
+/* cont_s.c - Send continue request to the server.
+ *
+ * Copyright (C) 2010, Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "libtac.h"
+#include "md5.h"
+
+/* this function sends a continue packet do TACACS+ server, asking
+ * for validation of given password
+ *
+ * return value:
+ * 0 : success
+ * < 0 : error status code, see LIBTAC_STATUS_...
+ * LIBTAC_STATUS_WRITE_ERR
+ * LIBTAC_STATUS_WRITE_TIMEOUT (pending impl)
+ * LIBTAC_STATUS_ASSEMBLY_ERR
+ */
+int tac_cont_send_seq(int fd, char *pass, int seq) {
+ HDR *th; /* TACACS+ packet header */
+ struct authen_cont tb; /* continue body */
+ int pass_len, bodylength, w;
+ int pkt_len = 0;
+ int ret = 0;
+ u_char *pkt = NULL;
+
+ th = _tac_req_header(TAC_PLUS_AUTHEN, 1);
+
+ /* set some header options */
+ th->version = TAC_PLUS_VER_0;
+ th->seq_no = seq; /* 1 = request, 2 = reply, 3 = continue, 4 = reply */
+ th->encryption = tac_encryption ? TAC_PLUS_ENCRYPTED_FLAG : TAC_PLUS_UNENCRYPTED_FLAG;
+
+ /* get size of submitted data */
+ pass_len = strlen(pass);
+
+ /* fill the body of message */
+ tb.user_msg_len = htons(pass_len);
+ tb.user_data_len = tb.flags = 0;
+
+ /* fill body length in header */
+ bodylength = TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE+0+pass_len;
+
+ th->datalength = htonl(bodylength);
+
+ /* we can now write the header */
+ w = write(fd, th, TAC_PLUS_HDR_SIZE);
+ if (w < 0 || w < TAC_PLUS_HDR_SIZE) {
+ TACSYSLOG((LOG_ERR, "%s: short write on header, wrote %d of %d: %m",\
+ __func__, w, TAC_PLUS_HDR_SIZE))
+ free(pkt);
+ free(th);
+ return LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ /* build the packet */
+ pkt = (u_char *) tac_xcalloc(1, bodylength);
+
+ bcopy(&tb, pkt+pkt_len, TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE); /* packet body beginning */
+ pkt_len += TAC_AUTHEN_CONT_FIXED_FIELDS_SIZE;
+ bcopy(pass, pkt+pkt_len, pass_len); /* password */
+ pkt_len += pass_len;
+
+ /* pkt_len == bodylength ? */
+ if (pkt_len != bodylength) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: bodylength %d != pkt_len %d",\
+ __func__, bodylength, pkt_len))
+ free(pkt);
+ free(th);
+ return LIBTAC_STATUS_ASSEMBLY_ERR;
+ }
+
+ /* encrypt the body */
+ _tac_crypt(pkt, th, bodylength);
+
+ w = write(fd, pkt, pkt_len);
+ if (w < 0 || w < pkt_len) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: short write on body, wrote %d of %d: %m",\
+ __func__, w, pkt_len))
+ ret=LIBTAC_STATUS_WRITE_ERR;
+ }
+
+ free(pkt);
+ free(th);
+ TACDEBUG((LOG_DEBUG, "%s: exit status=%d", __func__, ret))
+ return ret;
+} /* tac_cont_send */
diff --git a/libtac/lib/crypt.c b/libtac/lib/crypt.c
new file mode 100644
index 0000000..49a071c
--- /dev/null
+++ b/libtac/lib/crypt.c
@@ -0,0 +1,114 @@
+/* crypt.c - TACACS+ encryption related functions
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "libtac.h"
+#include "xalloc.h"
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#if defined(HAVE_OPENSSL_MD5_H) && defined(HAVE_LIBCRYPTO)
+# include <openssl/md5.h>
+#else
+# include "md5.h"
+# ifndef MD5_LBLOCK /* should be always, since it's in openssl */
+# define MD5_LBLOCK MD5_LEN
+# endif
+#endif
+
+/* Produce MD5 pseudo-random pad for TACACS+ encryption.
+ Use data from packet header and secret, which
+ should be a global variable */
+u_char *_tac_md5_pad(int len, HDR *hdr) {
+ int n, i, bufsize;
+ int bp = 0; /* buffer pointer */
+ int pp = 0; /* pad pointer */
+ u_char *pad;
+ u_char *buf;
+ MD5_CTX mdcontext;
+
+ /* make pseudo pad */
+ n = (int)(len/16)+1; /* number of MD5 runs */
+ bufsize = sizeof(hdr->session_id) + strlen(tac_secret) + sizeof(hdr->version)
+ + sizeof(hdr->seq_no) + MD5_LBLOCK + 10;
+ buf = (u_char *) tac_xcalloc(1, bufsize);
+ pad = (u_char *) tac_xcalloc(n, MD5_LBLOCK);
+
+ for (i=0; i<n; i++) {
+ /* MD5_1 = MD5{session_id, secret, version, seq_no}
+ MD5_2 = MD5{session_id, secret, version, seq_no, MD5_1} */
+
+ /* place session_id, key, version and seq_no in buffer */
+ bp = 0;
+ bcopy(&hdr->session_id, buf, sizeof(session_id));
+ bp += sizeof(session_id);
+ bcopy(tac_secret, buf+bp, strlen(tac_secret));
+ bp += strlen(tac_secret);
+ bcopy(&hdr->version, buf+bp, sizeof(hdr->version));
+ bp += sizeof(hdr->version);
+ bcopy(&hdr->seq_no, buf+bp, sizeof(hdr->seq_no));
+ bp += sizeof(hdr->seq_no);
+
+ /* append previous pad if this is not the first run */
+ if (i) {
+ bcopy(pad+((i-1)*MD5_LBLOCK), buf+bp, MD5_LBLOCK);
+ bp+=MD5_LBLOCK;
+ }
+
+#if defined(HAVE_OPENSSL_MD5_H) && defined(HAVE_LIBCRYPTO)
+ MD5_Init(&mdcontext);
+ MD5_Update(&mdcontext, buf, bp);
+ MD5_Final(pad+pp, &mdcontext);
+#else
+ MD5Init(&mdcontext);
+ MD5Update(&mdcontext, buf, bp);
+ MD5Final(pad+pp, &mdcontext);
+#endif
+
+ pp += MD5_LBLOCK;
+ }
+
+ free(buf);
+ return pad;
+
+} /* _tac_md5_pad */
+
+/* Perform encryption/decryption on buffer. This means simply XORing
+ each byte from buffer with according byte from pseudo-random
+ pad. */
+void _tac_crypt(u_char *buf, HDR *th, int length) {
+ int i;
+ u_char *pad;
+
+ /* null operation if no encryption requested */
+ if((tac_secret != NULL) && (th->encryption == TAC_PLUS_ENCRYPTED_FLAG)) {
+ pad = _tac_md5_pad(length, th);
+
+ for (i=0; i<length; i++) {
+ *(buf+i) ^= pad[i];
+ }
+
+ free(pad);
+ } else {
+ TACSYSLOG((LOG_WARNING, "%s: using no TACACS+ encryption", __func__))
+ }
+} /* _tac_crypt */
diff --git a/libtac/lib/hdr_check.c b/libtac/lib/hdr_check.c
new file mode 100644
index 0000000..fab09d8
--- /dev/null
+++ b/libtac/lib/hdr_check.c
@@ -0,0 +1,50 @@
+/* hdr_check.c - Perform basic sanity checks on received packet.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "messages.h"
+#include "libtac.h"
+
+/* Checks given reply header for possible inconsistencies:
+ * 1. reply type other than expected
+ * 2. sequence number other than 2 or 4
+ * 3. session_id different from one sent in request
+ * Returns pointer to error message
+ * or NULL when the header seems to be correct
+ */
+char *_tac_check_header(HDR *th, int type) {
+ if(th->type != type) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: unrelated reply, type %d, expected %d",\
+ __func__, th->type, type))
+ return protocol_err_msg;
+ } else if (1 == (th->seq_no % 2)) {
+ TACSYSLOG((LOG_ERR, "%s: not a reply - seq_no %d not even",\
+ __func__, th->seq_no))
+ return protocol_err_msg;
+ } /* else if(ntohl(th->session_id) != session_id) {
+ TACSYSLOG((LOG_ERR,\
+ "%s: unrelated reply, received session_id %d != sent %d",\
+ __func__, ntohl(th->session_id), session_id))
+ return protocol_err_msg;
+ } */
+
+ return NULL; /* header is ok */
+} /* check header */
diff --git a/libtac/lib/header.c b/libtac/lib/header.c
new file mode 100644
index 0000000..b746a05
--- /dev/null
+++ b/libtac/lib/header.c
@@ -0,0 +1,94 @@
+/* header.c - Create pre-filled header for TACACS+ request.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "libtac.h"
+#include "xalloc.h"
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO)
+# include <openssl/rand.h>
+#else
+# include "magic.h"
+#endif
+
+/* Miscellaneous variables that are global, because we need
+ * store their values between different functions and connections.
+ */
+/* Session identifier. */
+int session_id;
+
+/* Encryption flag. */
+int tac_encryption = 0;
+
+/* Pointer to TACACS+ shared secret string. */
+/* note: tac_secret will point to tacplus_server[i].key */
+const char *tac_secret = NULL;
+
+/* TACACS+ shared login string. */
+char tac_login[64]; /* default is PAP */
+
+/* priv_lvl */
+int tac_priv_lvl = TAC_PLUS_PRIV_LVL_MIN;
+
+/* Authentication Method */
+int tac_authen_method = TAC_PLUS_AUTHEN_METH_TACACSPLUS;
+
+/* Service requesting authentication */
+int tac_authen_service = TAC_PLUS_AUTHEN_SVC_PPP;
+
+/* additional runtime flags */
+
+int tac_debug_enable = 0;
+int tac_readtimeout_enable = 0;
+
+/* Returns pre-filled TACACS+ packet header of given type.
+ * 1. you MUST fill th->datalength and th->version
+ * 2. you MAY fill th->encryption
+ * 3. you are responsible for freeing allocated header
+ * By default packet encryption is enabled. The version
+ * field depends on the TACACS+ request type and thus it
+ * cannot be predefined.
+ */
+HDR *_tac_req_header(u_char type, int cont_session) {
+ HDR *th;
+
+ th=(HDR *) tac_xcalloc(1, TAC_PLUS_HDR_SIZE);
+
+ /* preset some packet options in header */
+ th->type=type;
+ th->seq_no=1; /* always 1 for request */
+ th->encryption=TAC_PLUS_ENCRYPTED_FLAG;
+
+ /* make session_id from pseudo-random number */
+ if (!cont_session) {
+#if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO)
+ RAND_pseudo_bytes((unsigned char *) &session_id, sizeof(session_id));
+#else
+ session_id = tac_magic();
+#endif
+ }
+ th->session_id = htonl(session_id);
+
+ return th;
+}
diff --git a/libtac/lib/magic.c b/libtac/lib/magic.c
new file mode 100644
index 0000000..d42e620
--- /dev/null
+++ b/libtac/lib/magic.c
@@ -0,0 +1,88 @@
+/* magic.c - PPP Magic Number routines.
+ *
+ * Copyright (C) 1989 Carnegie Mellon University.
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+/* if OpenSSL library is available this legacy code will not be compiled in */
+#if !defined(HAVE_OPENSSL_RAND_H) && !defined(HAVE_LIBCRYPTO)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "magic.h"
+
+static int magic_initialised = 0;
+
+/*
+ * magic_init - Initialize the magic number generator.
+ *
+ * Attempts to compute a random number seed which will not repeat.
+ */
+static void
+tac_magic_init()
+{
+ struct stat statbuf;
+ long seed = 0;
+ struct timeval t;
+
+ if (magic_initialised)
+ return;
+
+ // try to initialise seed from urandom
+ if (!lstat("/dev/urandom", &statbuf) && S_ISCHR(statbuf.st_mode)) {
+ int rfd = open("/dev/urandom", O_RDONLY);
+ if(rfd >= 0) {
+ if (read(rfd, &seed, sizeof(seed)) < 0)
+ seed = 0;
+ close(rfd);
+ }
+ }
+
+ // fallback
+ gettimeofday(&t, NULL);
+ seed ^= gethostid() ^ t.tv_sec ^ t.tv_usec ^ getpid();
+
+ // finally seed the PRNG
+ srandom(seed);
+ magic_initialised = 1;
+}
+
+#include <pthread.h>
+/*
+ * magic - Returns the next magic number.
+ */
+u_int32_t
+tac_magic()
+{
+ static pthread_once_t magic_control = PTHREAD_ONCE_INIT;
+
+ pthread_once(&magic_control, &tac_magic_init);
+
+ return (u_int32_t)random();
+}
+
+#endif
diff --git a/libtac/lib/magic.h b/libtac/lib/magic.h
new file mode 100644
index 0000000..b03be94
--- /dev/null
+++ b/libtac/lib/magic.h
@@ -0,0 +1,30 @@
+/* magic.h - PPP Magic Number definitions.
+ *
+ * Copyright (C) 1989 Carnegie Mellon University.
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef _MAGIC_H
+#define _MAGIC_H
+
+#include "libtac.h"
+
+__BEGIN_DECLS
+u_int32_t tac_magic __P((void)); /* Returns the next magic number */
+__END_DECLS
+
+#endif
diff --git a/libtac/lib/md5.c b/libtac/lib/md5.c
new file mode 100644
index 0000000..fe29f41
--- /dev/null
+++ b/libtac/lib/md5.c
@@ -0,0 +1,273 @@
+/* md5.c - the source code for MD5 routines
+ *
+ * Copyright (C) 1990, RSA Data Security, Inc.
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+/* if OpenSSL library is available this legacy code will not be compiled in */
+#if !defined(HAVE_OPENSSL_MD5_H) && !defined(HAVE_LIBCRYPTO)
+
+#include <string.h>
+#include "md5.h"
+
+/* forward declaration */
+static void Transform __P((UINT4 *buf, UINT4 *in));
+
+static unsigned char PADDING[64] = {
+ 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+/* F, G, H and I are basic MD5 functions */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */
+/* Rotation is separate from addition to prevent recomputation */
+#define FF(a, b, c, d, x, s, ac) \
+ {(a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) \
+ {(a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) \
+ {(a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) \
+ {(a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+#ifdef __STDC__
+#define UL(x) x##U
+#else
+#define UL(x) x
+#endif
+
+/* The routine MD5Init initializes the message-digest context
+ mdContext. All fields are set to zero.
+ */
+void MD5Init (MD5_CTX *mdContext) {
+ mdContext->i[0] = mdContext->i[1] = (UINT4)0;
+
+ /* Load magic initialization constants. */
+ mdContext->buf[0] = (UINT4)0x67452301;
+ mdContext->buf[1] = (UINT4)0xefcdab89;
+ mdContext->buf[2] = (UINT4)0x98badcfe;
+ mdContext->buf[3] = (UINT4)0x10325476;
+}
+
+/* The routine MD5Update updates the message-digest context to
+ account for the presence of each of the characters inBuf[0..inLen-1]
+ in the message whose digest is being computed.
+ */
+void MD5Update ( MD5_CTX *mdContext, unsigned char *inBuf,
+ unsigned int inLen) {
+
+ UINT4 in[16];
+ int mdi;
+ unsigned int i, ii;
+
+ /* compute number of bytes mod 64 */
+ mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+ /* update number of bits */
+ if ((mdContext->i[0] + ((UINT4)inLen << 3)) < mdContext->i[0])
+ mdContext->i[1]++;
+ mdContext->i[0] += ((UINT4)inLen << 3);
+ mdContext->i[1] += ((UINT4)inLen >> 29);
+
+ while (inLen--) {
+ /* add new character to buffer, increment mdi */
+ mdContext->in[mdi++] = *inBuf++;
+
+ /* transform if necessary */
+ if (mdi == 0x40) {
+ for (i = 0, ii = 0; i < 16; i++, ii += 4)
+ in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+ (((UINT4)mdContext->in[ii+2]) << 16) |
+ (((UINT4)mdContext->in[ii+1]) << 8) |
+ ((UINT4)mdContext->in[ii]);
+ Transform (mdContext->buf, in);
+ mdi = 0;
+ }
+ }
+}
+
+/* The routine MD5Final terminates the message-digest computation and
+ ends with the desired message digest in mdContext->digest[0...15].
+ */
+void MD5Final (unsigned char hash[], MD5_CTX *mdContext) {
+ UINT4 in[16];
+ int mdi;
+ unsigned int i, ii;
+ unsigned int padLen;
+
+ /* save number of bits */
+ in[14] = mdContext->i[0];
+ in[15] = mdContext->i[1];
+
+ /* compute number of bytes mod 64 */
+ mdi = (int)((mdContext->i[0] >> 3) & 0x3F);
+
+ /* pad out to 56 mod 64 */
+ padLen = (mdi < 56) ? (56 - mdi) : (120 - mdi);
+ MD5Update (mdContext, PADDING, padLen);
+
+ /* append length in bits and transform */
+ for (i = 0, ii = 0; i < 14; i++, ii += 4)
+ in[i] = (((UINT4)mdContext->in[ii+3]) << 24) |
+ (((UINT4)mdContext->in[ii+2]) << 16) |
+ (((UINT4)mdContext->in[ii+1]) << 8) |
+ ((UINT4)mdContext->in[ii]);
+ Transform (mdContext->buf, in);
+
+ /* store buffer in digest */
+ for (i = 0, ii = 0; i < 4; i++, ii += 4) {
+ mdContext->digest[ii] = (unsigned char)(mdContext->buf[i] & 0xFF);
+ mdContext->digest[ii+1] =
+ (unsigned char)((mdContext->buf[i] >> 8) & 0xFF);
+ mdContext->digest[ii+2] =
+ (unsigned char)((mdContext->buf[i] >> 16) & 0xFF);
+ mdContext->digest[ii+3] =
+ (unsigned char)((mdContext->buf[i] >> 24) & 0xFF);
+ }
+ memcpy(hash, mdContext->digest, 16);
+}
+
+/* Basic MD5 step. Transforms buf based on in.
+ */
+static void Transform ( UINT4 *buf, UINT4 *in) {
+ UINT4 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+ /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+ FF ( a, b, c, d, in[ 0], S11, UL(3614090360)); /* 1 */
+ FF ( d, a, b, c, in[ 1], S12, UL(3905402710)); /* 2 */
+ FF ( c, d, a, b, in[ 2], S13, UL( 606105819)); /* 3 */
+ FF ( b, c, d, a, in[ 3], S14, UL(3250441966)); /* 4 */
+ FF ( a, b, c, d, in[ 4], S11, UL(4118548399)); /* 5 */
+ FF ( d, a, b, c, in[ 5], S12, UL(1200080426)); /* 6 */
+ FF ( c, d, a, b, in[ 6], S13, UL(2821735955)); /* 7 */
+ FF ( b, c, d, a, in[ 7], S14, UL(4249261313)); /* 8 */
+ FF ( a, b, c, d, in[ 8], S11, UL(1770035416)); /* 9 */
+ FF ( d, a, b, c, in[ 9], S12, UL(2336552879)); /* 10 */
+ FF ( c, d, a, b, in[10], S13, UL(4294925233)); /* 11 */
+ FF ( b, c, d, a, in[11], S14, UL(2304563134)); /* 12 */
+ FF ( a, b, c, d, in[12], S11, UL(1804603682)); /* 13 */
+ FF ( d, a, b, c, in[13], S12, UL(4254626195)); /* 14 */
+ FF ( c, d, a, b, in[14], S13, UL(2792965006)); /* 15 */
+ FF ( b, c, d, a, in[15], S14, UL(1236535329)); /* 16 */
+
+ /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+ GG ( a, b, c, d, in[ 1], S21, UL(4129170786)); /* 17 */
+ GG ( d, a, b, c, in[ 6], S22, UL(3225465664)); /* 18 */
+ GG ( c, d, a, b, in[11], S23, UL( 643717713)); /* 19 */
+ GG ( b, c, d, a, in[ 0], S24, UL(3921069994)); /* 20 */
+ GG ( a, b, c, d, in[ 5], S21, UL(3593408605)); /* 21 */
+ GG ( d, a, b, c, in[10], S22, UL( 38016083)); /* 22 */
+ GG ( c, d, a, b, in[15], S23, UL(3634488961)); /* 23 */
+ GG ( b, c, d, a, in[ 4], S24, UL(3889429448)); /* 24 */
+ GG ( a, b, c, d, in[ 9], S21, UL( 568446438)); /* 25 */
+ GG ( d, a, b, c, in[14], S22, UL(3275163606)); /* 26 */
+ GG ( c, d, a, b, in[ 3], S23, UL(4107603335)); /* 27 */
+ GG ( b, c, d, a, in[ 8], S24, UL(1163531501)); /* 28 */
+ GG ( a, b, c, d, in[13], S21, UL(2850285829)); /* 29 */
+ GG ( d, a, b, c, in[ 2], S22, UL(4243563512)); /* 30 */
+ GG ( c, d, a, b, in[ 7], S23, UL(1735328473)); /* 31 */
+ GG ( b, c, d, a, in[12], S24, UL(2368359562)); /* 32 */
+
+ /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+ HH ( a, b, c, d, in[ 5], S31, UL(4294588738)); /* 33 */
+ HH ( d, a, b, c, in[ 8], S32, UL(2272392833)); /* 34 */
+ HH ( c, d, a, b, in[11], S33, UL(1839030562)); /* 35 */
+ HH ( b, c, d, a, in[14], S34, UL(4259657740)); /* 36 */
+ HH ( a, b, c, d, in[ 1], S31, UL(2763975236)); /* 37 */
+ HH ( d, a, b, c, in[ 4], S32, UL(1272893353)); /* 38 */
+ HH ( c, d, a, b, in[ 7], S33, UL(4139469664)); /* 39 */
+ HH ( b, c, d, a, in[10], S34, UL(3200236656)); /* 40 */
+ HH ( a, b, c, d, in[13], S31, UL( 681279174)); /* 41 */
+ HH ( d, a, b, c, in[ 0], S32, UL(3936430074)); /* 42 */
+ HH ( c, d, a, b, in[ 3], S33, UL(3572445317)); /* 43 */
+ HH ( b, c, d, a, in[ 6], S34, UL( 76029189)); /* 44 */
+ HH ( a, b, c, d, in[ 9], S31, UL(3654602809)); /* 45 */
+ HH ( d, a, b, c, in[12], S32, UL(3873151461)); /* 46 */
+ HH ( c, d, a, b, in[15], S33, UL( 530742520)); /* 47 */
+ HH ( b, c, d, a, in[ 2], S34, UL(3299628645)); /* 48 */
+
+ /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+ II ( a, b, c, d, in[ 0], S41, UL(4096336452)); /* 49 */
+ II ( d, a, b, c, in[ 7], S42, UL(1126891415)); /* 50 */
+ II ( c, d, a, b, in[14], S43, UL(2878612391)); /* 51 */
+ II ( b, c, d, a, in[ 5], S44, UL(4237533241)); /* 52 */
+ II ( a, b, c, d, in[12], S41, UL(1700485571)); /* 53 */
+ II ( d, a, b, c, in[ 3], S42, UL(2399980690)); /* 54 */
+ II ( c, d, a, b, in[10], S43, UL(4293915773)); /* 55 */
+ II ( b, c, d, a, in[ 1], S44, UL(2240044497)); /* 56 */
+ II ( a, b, c, d, in[ 8], S41, UL(1873313359)); /* 57 */
+ II ( d, a, b, c, in[15], S42, UL(4264355552)); /* 58 */
+ II ( c, d, a, b, in[ 6], S43, UL(2734768916)); /* 59 */
+ II ( b, c, d, a, in[13], S44, UL(1309151649)); /* 60 */
+ II ( a, b, c, d, in[ 4], S41, UL(4149444226)); /* 61 */
+ II ( d, a, b, c, in[11], S42, UL(3174756917)); /* 62 */
+ II ( c, d, a, b, in[ 2], S43, UL( 718787259)); /* 63 */
+ II ( b, c, d, a, in[ 9], S44, UL(3951481745)); /* 64 */
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+#endif
diff --git a/libtac/lib/md5.h b/libtac/lib/md5.h
new file mode 100644
index 0000000..f0289fe
--- /dev/null
+++ b/libtac/lib/md5.h
@@ -0,0 +1,45 @@
+/* md5.h - header file for implementation of MD5
+ *
+ * Copyright (C) 1990, RSA Data Security, Inc.
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef __MD5_INCLUDE__
+
+#include "libtac.h"
+
+/* typedef a 32-bit type */
+typedef unsigned int UINT4;
+
+/* Data structure for MD5 (Message-Digest) computation */
+typedef struct {
+ UINT4 i[2]; /* number of _bits_ handled mod 2^64 */
+ UINT4 buf[4]; /* scratch buffer */
+ unsigned char in[64]; /* input buffer */
+ unsigned char digest[16]; /* actual digest after MD5Final call */
+} MD5_CTX;
+
+__BEGIN_DECLS
+void MD5Init __P((MD5_CTX*));
+void MD5Update __P((MD5_CTX*, unsigned char*, UINT4));
+void MD5Final __P((unsigned char[], MD5_CTX*));
+__END_DECLS
+
+#define MD5_LEN 16
+
+#define __MD5_INCLUDE__
+#endif /* __MD5_INCLUDE__ */
diff --git a/libtac/lib/messages.c b/libtac/lib/messages.c
new file mode 100644
index 0000000..65645f7
--- /dev/null
+++ b/libtac/lib/messages.c
@@ -0,0 +1,31 @@
+/* messages.c - Various messages returned to user.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+char *protocol_err_msg = "(Protocol error)";
+char *authen_syserr_msg = "(Authentication system error)";
+char *author_ok_msg = "(Service granted)";
+char *author_fail_msg = "(Service not allowed)";
+char *author_err_msg = "(Service not allowed. Server error)";
+char *author_syserr_msg = "(Authorization system error)";
+char *acct_ok_msg = "(Accounted ok)";
+char *acct_fail_msg = "(Accounting failed)";
+char *acct_err_msg = "(Accounting failed. Server error)";
+char *acct_syserr_msg = "(Accounting system error)";
diff --git a/libtac/lib/messages.h b/libtac/lib/messages.h
new file mode 100644
index 0000000..1561c08
--- /dev/null
+++ b/libtac/lib/messages.h
@@ -0,0 +1,36 @@
+/* messages.h
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef _MESSAGES_H
+#define _MESSAGES_H
+
+extern char *protocol_err_msg;
+extern char *authen_syserr_msg;
+extern char *author_ok_msg;
+extern char *author_fail_msg;
+extern char *author_err_msg;
+extern char *author_syserr_msg;
+extern char *acct_ok_msg;
+extern char *acct_fail_msg;
+extern char *acct_err_msg;
+extern char *acct_syserr_msg;
+
+#endif
diff --git a/libtac/lib/read_wait.c b/libtac/lib/read_wait.c
new file mode 100644
index 0000000..36ea19e
--- /dev/null
+++ b/libtac/lib/read_wait.c
@@ -0,0 +1,136 @@
+/* read_wait.c - Wait for data to read on a fd.
+ *
+ * Copyright (C) 2011, Darren Besler (dbesler@beehive.mb.ca)
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#define _GNU_SOURCE /* for POLLRDHUP */
+
+#include <sys/time.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+
+#include "libtac.h"
+
+/* FIONREAD support for sun */
+#ifdef sun
+#include <sys/filio.h>
+#endif
+
+static int delta_msecs(struct timeval *newer, struct timeval *older) {
+ time_t deltasecs;
+ suseconds_t deltausecs;
+ struct timeval now;
+
+ if (newer == NULL) {
+ gettimeofday(&now, NULL);
+ newer = &now;
+ }
+
+ deltasecs = newer->tv_sec - older->tv_sec;
+ if ( newer->tv_usec < older->tv_usec ) {
+ deltasecs--;
+ deltausecs = (1000000+newer->tv_usec) - older->tv_usec;
+ } else {
+ deltausecs = newer->tv_usec - older->tv_usec;
+ }
+ return (deltasecs*1000)+(deltausecs/1000);
+}
+
+
+/*
+ * tac_read_wait
+ *
+ * Parms:
+ * fd - open fd to wait on till data avail, or timeout
+ * timeout - maximum time to wait in milliseconds
+ * size - amount of data to wait for
+ * 0 : any amount of data
+ * >0 : amount of data to wait for
+ * timeleft - return of time left from timeout
+ *
+ * Returns:
+ * 0 - data avail, no timeout
+ * -1 - timeout
+ * n - errno
+ */
+
+int tac_read_wait(int fd, int timeout, int size, int *time_left) {
+ int retval = 0;
+ int remaining;
+ struct pollfd fds[1];
+
+ struct timeval start;
+
+ gettimeofday(&start, NULL);
+
+ /* setup for read timeout.
+ * will use poll() as it provides greatest compatibility
+ * vs setsockopt(SO_RCVTIMEO) which isn't supported on Solaris
+ */
+
+ remaining = timeout; /* in msecs */
+
+ fds[0].fd = fd;
+ /*
+ * should probably have a feature test for POLLRDHUP for non-linux.
+ * It's been in linux and glibc for many years
+ */
+ fds[0].events = POLLIN | POLLRDHUP;
+
+ while (remaining > 0) {
+ int rc;
+ int avail = 0;
+ rc = poll(fds, 1, remaining);
+ remaining = timeout - delta_msecs(NULL, &start);
+ if (remaining < 0)
+ remaining = 0;
+ if ( time_left != NULL ) {
+ *time_left = remaining;
+ }
+
+ /* why did poll return */
+ if (rc == 0) { /* Receive timeout */
+ retval = -1;
+ break;
+ }
+
+ if (rc > 0) { /* there is data available */
+ if (size > 0 && /* check for enuf available? */
+ ioctl(fd,FIONREAD,(char*)&avail) == 0 && avail < size) {
+ if(fds[0].revents & POLLRDHUP) {
+ /* other side closed the socket, stop polling */
+ retval = -1;
+ break;
+ }
+ continue; /* not enuf yet, wait for more */
+ } else {
+ break;
+ }
+ }
+
+ if (rc < 0 && errno == EINTR) { /* interrupt */
+ continue;
+ }
+
+ /* all other conditions is an error */
+ retval = errno;
+ break;
+ }
+ return remaining == 0 ? -1 : retval;
+} /* read_wait */
diff --git a/libtac/lib/version.c b/libtac/lib/version.c
new file mode 100644
index 0000000..fb51a4f
--- /dev/null
+++ b/libtac/lib/version.c
@@ -0,0 +1,24 @@
+/* version.c - TACACS+ library version.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+int tac_ver_major = 1;
+int tac_ver_minor = 10;
+int tac_ver_patch = 0; /* patchlevel */
diff --git a/libtac/lib/xalloc.c b/libtac/lib/xalloc.c
new file mode 100644
index 0000000..3f52b6e
--- /dev/null
+++ b/libtac/lib/xalloc.c
@@ -0,0 +1,78 @@
+/* xalloc.c - Failsafe memory allocation functions.
+ * Taken from excellent glibc.info ;)
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "xalloc.h"
+
+void *tac_xcalloc(size_t nmemb, size_t size) {
+ void *val = calloc(nmemb, size);
+ if(val == 0) {
+ TACSYSLOG((LOG_ERR, "%s: calloc(%u,%u) failed", __func__,\
+ (unsigned) nmemb, (unsigned) size))
+ exit(1);
+ }
+ return val;
+}
+
+void *tac_xrealloc(void *ptr, size_t size) {
+ void *val = realloc(ptr, size);
+ if(val == 0) {
+ TACSYSLOG((LOG_ERR, "%s: realloc(%u) failed", __func__, (unsigned) size))
+ exit(1);
+ }
+ return val;
+}
+
+char *tac_xstrdup(const char *s) {
+ char *p;
+ if (s == NULL) return NULL;
+
+ if ( (p = strdup(s)) == NULL ) {
+ TACSYSLOG((LOG_ERR, "%s: strdup(%s) failed: %m", __func__, s))
+ exit(1);
+ }
+ return p;
+}
+
+
+/*
+ safe string copy that aborts when destination buffer is too small
+*/
+char *tac_xstrcpy(char *dst, const char *src, size_t dst_size) {
+ if (dst == NULL) {
+ TACSYSLOG((LOG_ERR, "tac_xstrcpy(): dst == NULL"));
+ abort();
+ }
+ if (src == NULL) {
+ TACSYSLOG((LOG_ERR, "tac_xstrcpy(): src == NULL"));
+ abort();
+ }
+ if (!dst_size)
+ return NULL;
+
+ if (strlen(src) >= dst_size) {
+ TACSYSLOG((LOG_ERR, "tac_xstrcpy(): argument too long, aborting"));
+ abort();
+ }
+
+ return strcpy(dst, src);
+}
+
diff --git a/libtac/lib/xalloc.h b/libtac/lib/xalloc.h
new file mode 100644
index 0000000..bfc1ce4
--- /dev/null
+++ b/libtac/lib/xalloc.h
@@ -0,0 +1,33 @@
+/* xalloc.h
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef _XALLOC_H
+#define _XALLOC_H
+
+#include "libtac.h"
+
+__BEGIN_DECLS
+extern void *tac_xcalloc(size_t nmemb, size_t size);
+extern void *tac_xrealloc(void *ptr, size_t size);
+extern char *tac_xstrdup(const char *s);
+__END_DECLS
+
+#endif
diff --git a/pam_tacplus.c b/pam_tacplus.c
new file mode 100644
index 0000000..2f131e3
--- /dev/null
+++ b/pam_tacplus.c
@@ -0,0 +1,1104 @@
+/* pam_tacplus.c - PAM interface for TACACS+ protocol.
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * Copyright 2015, 2016, 2017, 2018 Cumulus Networks, Inc. All rights reserved.
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#include "pam_tacplus.h"
+#include "support.h"
+
+#include <stdlib.h> /* malloc */
+#include <stdio.h>
+#include <syslog.h>
+#include <netdb.h> /* gethostbyname */
+#include <sys/socket.h> /* in_addr */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdarg.h> /* va_ */
+#include <signal.h>
+#include <string.h> /* strdup */
+#include <ctype.h>
+#include <time.h>
+#include <unistd.h>
+#include <strings.h>
+
+#ifdef HAVE_CONFIG_H
+ #include "config.h"
+#endif
+
+#if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO)
+# include <openssl/rand.h>
+#else
+# include "libtac/lib/magic.h"
+#endif
+
+/* address of server discovered by pam_sm_authenticate */
+tacplus_server_t active_server;
+
+extern char *__vrfname;
+
+/* privilege level, used for mapping from tacacs userid to local
+ * tacacs{0...15} user
+ */
+static unsigned priv_level;
+
+/* accounting task identifier */
+static short unsigned int task_id = 0;
+
+
+/* Helper functions */
+int _pam_send_account(pam_handle_t *pamh, int tac_fd, int type,
+ const char *user, char *tty, char *r_addr, char *cmd) {
+ char buf[64];
+ struct tac_attrib *attr = NULL;
+ int retval = -1;
+ struct areply re;
+
+ re.msg = NULL;
+ snprintf(buf, sizeof buf, "%lu", (unsigned long)time(NULL));
+
+ if (type == TAC_PLUS_ACCT_FLAG_START) {
+ tac_add_attrib(&attr, "start_time", buf);
+ } else if (type == TAC_PLUS_ACCT_FLAG_STOP) {
+ tac_add_attrib(&attr, "stop_time", buf);
+ }
+ snprintf(buf, sizeof buf, "%hu", task_id);
+ tac_add_attrib(&attr, "task_id", buf);
+ tac_add_attrib(&attr, "service", tac_service);
+ if(tac_protocol[0] != '\0')
+ tac_add_attrib(&attr, "protocol", tac_protocol);
+ if (cmd != NULL) {
+ tac_add_attrib(&attr, "cmd", cmd);
+ }
+
+ retval = tac_acct_send(tac_fd, type, user, tty, r_addr, attr);
+
+ /* attribute is no longer needed */
+ tac_free_attrib(&attr);
+
+ if(retval < 0) {
+ pam_syslog(pamh, LOG_WARNING, "%s: send %s accounting failed"
+ " (task %hu)", __func__, tac_acct_flag2str(type), task_id);
+ }
+ else if( tac_acct_read(tac_fd, &re) != TAC_PLUS_ACCT_STATUS_SUCCESS ) {
+ pam_syslog(pamh, LOG_WARNING, "%s: accounting %s failed (task %hu)",
+ __func__, tac_acct_flag2str(type), task_id);
+ retval = -1;
+ }
+ else
+ retval = 0;
+
+ if(re.msg != NULL)
+ free(re.msg);
+
+ return retval;
+}
+
+
+/*
+ * If all servers have been unresponsive, clear that state, so we try
+ * them all. It might have been transient.
+ */
+static void tac_chk_anyresp(void)
+{
+ int i, anyok=0;
+
+ for(i = 0; i < tac_srv_no; i++) {
+ if (!tac_srv[i].not_resp)
+ anyok++;
+ }
+ if (!anyok) {
+ for(i = 0; i < tac_srv_no; i++)
+ tac_srv[i].not_resp = 0;
+ }
+}
+
+
+/*
+ * Send an accounting record to the TACACS+ server.
+ * We send the start/stop accounting records even if the user is not known
+ * to the TACACS+ server. This seems non-intuitive, but it's the way
+ * this code is written to work.
+ */
+int _pam_account(pam_handle_t *pamh, int argc, const char **argv,
+ int type, char *cmd) {
+
+ int retval;
+ int ctrl;
+ char *user = NULL;
+ char *tty = NULL;
+ char *r_addr = NULL;
+ char *typemsg;
+ int status = PAM_SESSION_ERR;
+ int srv_i, tac_fd;
+
+ typemsg = tac_acct_flag2str(type);
+ ctrl = _pam_parse (pamh, argc, argv);
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: [%s] called (pam_tacplus"
+ " v%u.%u.%u)", __func__, typemsg, PAM_TAC_VMAJ, PAM_TAC_VMIN,
+ PAM_TAC_VPAT);
+
+ _pam_get_user(pamh, &user);
+ if (user == NULL)
+ return PAM_USER_UNKNOWN;
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: username [%s] obtained", __func__,
+ user);
+
+ if (!task_id)
+#if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO)
+ RAND_pseudo_bytes((unsigned char *) &task_id, sizeof(task_id));
+#else
+ task_id = (short unsigned int) tac_magic();
+#endif
+
+ _pam_get_terminal(pamh, &tty);
+ if(!strncmp(tty, "/dev/", 5))
+ tty += 5;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: tty [%s] obtained", __func__, tty);
+
+ _pam_get_rhost(pamh, &r_addr);
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: rhost [%s] obtained", __func__,
+ r_addr);
+
+ /* checks for specific data required by TACACS+, which should
+ be supplied in command line */
+ if(tac_protocol[0] == '\0') {
+ pam_syslog(pamh, LOG_ERR, "ACC: TACACS+ protocol type not configured");
+ return PAM_AUTHINFO_UNAVAIL;
+ }
+
+ /* when this module is called from within pppd or other
+ application dealing with serial lines, it is likely
+ that we will get hit with signal caused by modem hangup;
+ this is important only for STOP packets, it's relatively
+ rare that modem hangs up on accounting start */
+ if(type == TAC_PLUS_ACCT_FLAG_STOP) {
+ signal(SIGALRM, SIG_IGN);
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ }
+
+ /*
+ * If PAM_SESSION_ERR is used, then the pam config can't
+ * ignore server failures, so use PAM_AUTHINFO_UNAVAIL.
+ *
+ * We have to make a new connection each time, because libtac is single
+ * threaded (doesn't support multiple connects at the same time due to
+ * use of globals)), and doesn't have support for persistent connections.
+ * That's fixable, but not worth the effort at this point.
+ *
+ * TODO: this should be converted to use do_tac_connect eventually.
+ */
+ status = PAM_AUTHINFO_UNAVAIL;
+ tac_chk_anyresp();
+ for(srv_i = 0; srv_i < tac_srv_no; srv_i++) {
+ if (tac_srv[srv_i].not_resp)
+ continue; /* don't retry if previously not responding */
+ tac_fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key,
+ tac_src_addr_info, __vrfname);
+ if (tac_fd < 0) {
+ pam_syslog(pamh, LOG_WARNING, "%s: error sending %s (fd)", __func__,
+ typemsg);
+ tac_srv[srv_i].not_resp = 1;
+ continue;
+ }
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: connected with fd=%d"
+ " to srv[%d] %s type=%s", __func__, tac_fd, srv_i,
+ tac_srv[srv_i].addr ? tac_ntop(tac_srv[srv_i].addr->ai_addr)
+ : "not set", typemsg);
+
+ retval = _pam_send_account(pamh, tac_fd, type, user, tty, r_addr, cmd);
+ close(tac_fd);
+ if (retval < 0) {
+ pam_syslog(pamh, LOG_WARNING, "%s: error sending %s (acct)",
+ __func__, typemsg);
+ } else {
+ status = PAM_SUCCESS;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: [%s] for [%s] sent",
+ __func__, typemsg, user);
+ }
+
+ if ((status == PAM_SUCCESS) && !(ctrl & PAM_TAC_ACCT)) {
+ /* do not send acct start/stop packets to _all_ servers */
+ break;
+ }
+ }
+
+ if (type == TAC_PLUS_ACCT_FLAG_STOP) {
+ signal(SIGALRM, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ }
+ return status;
+}
+
+
+/*
+ * Talk to the server for authentication
+ */
+static int tac_auth_converse(int ctrl, int fd, int *sptr,
+ char *pass, pam_handle_t * pamh) {
+ int msg;
+ int ret = 1;
+ struct areply re = { .attr = NULL, .msg = NULL, .status = 0, .flags = 0 };
+ struct pam_message conv_msg = { .msg_style = 0, .msg = NULL };
+ struct pam_response *resp = NULL;
+
+ msg = tac_authen_read(fd, &re);
+
+ if (NULL != re.msg) {
+ conv_msg.msg = re.msg;
+ }
+
+ /* talk the protocol */
+ switch (msg) {
+ case TAC_PLUS_AUTHEN_STATUS_PASS:
+ /* success */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_PASS");
+ if (NULL != conv_msg.msg) {
+ int retval = -1;
+
+ conv_msg.msg_style = PAM_TEXT_INFO;
+ retval = converse(pamh, 1, &conv_msg, &resp);
+ if (PAM_SUCCESS == retval) {
+ if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG))
+ pam_syslog(pamh, LOG_DEBUG, "send msg=\"%s\"",
+ conv_msg.msg);
+ }
+ else {
+ pam_syslog(pamh, LOG_WARNING, "%s: error sending"
+ " msg=\"%s\", retval=%d", __func__, conv_msg.msg,
+ retval);
+ }
+ }
+ *sptr = PAM_SUCCESS;
+ ret = 0;
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_FAIL:
+ /*
+ * We've already validated that the user is known, so
+ * this will be a password mismatch, or user not permitted
+ * on this host, or at this time of day, etc.
+ */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_FAIL");
+ if (NULL != conv_msg.msg) {
+ int retval = -1;
+
+ conv_msg.msg_style = PAM_ERROR_MSG;
+ retval = converse(pamh, 1, &conv_msg, &resp);
+ if (PAM_SUCCESS == retval) {
+ if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG))
+ pam_syslog(pamh, LOG_DEBUG, "send msg=\"%s\"",
+ conv_msg.msg);
+ }
+ else {
+ pam_syslog(pamh, LOG_WARNING, "%s: error sending msg="
+ "\"%s\", retval=%d", __func__, conv_msg.msg, retval);
+ }
+ }
+
+ *sptr = PAM_AUTH_ERR;
+ ret = 0;
+ pam_syslog(pamh, LOG_NOTICE, "auth failed %d", msg);
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_GETDATA:
+ /* not implemented */
+ if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG))
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_GETDATA");
+
+ if (NULL != conv_msg.msg) {
+ int retval = -1;
+ int echo_off = (0x1 == (re.flags & 0x1));
+
+ conv_msg.msg_style = echo_off ? PAM_PROMPT_ECHO_OFF :
+ PAM_PROMPT_ECHO_ON;
+ retval = converse(pamh, 1, &conv_msg, &resp);
+ if (PAM_SUCCESS == retval) {
+ if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG))
+ pam_syslog(pamh, LOG_DEBUG, "sent msg=\"%s\", resp="
+ "\"%s\"", conv_msg.msg, resp->resp);
+
+ if (PAM_TAC_DEBUG == (ctrl & PAM_TAC_DEBUG))
+ pam_syslog(pamh, LOG_DEBUG, "%s: calling"
+ " tac_cont_send", __func__);
+
+ if (0 > tac_cont_send_seq(fd, resp->resp, re.seq_no + 1)) {
+ pam_syslog(pamh, LOG_ERR, "error sending continue req"
+ " to TACACS+ server");
+ *sptr = PAM_AUTHINFO_UNAVAIL;
+ }
+ }
+ else {
+ pam_syslog(pamh, LOG_WARNING, "%s: error sending msg="
+ "\"%s\", retval=%d (%s)", __func__, conv_msg.msg,
+ retval, pam_strerror(pamh, retval));
+ *sptr = PAM_AUTHINFO_UNAVAIL;
+ }
+ }
+ else {
+ pam_syslog(pamh, LOG_ERR, "GETDATA response with no message,"
+ " returning PAM_AUTHINFO_UNAVAIL");
+
+ *sptr = PAM_AUTHINFO_UNAVAIL;
+ }
+
+ ret = 0;
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_GETUSER:
+ /* not implemented */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_GETUSER");
+
+ ret = 0;
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_GETPASS:
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_GETPASS");
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: tac_cont_send called",
+ __func__);
+
+ if (tac_cont_send(fd, pass) < 0) {
+ pam_syslog(pamh, LOG_ERR, "error sending continue req to"
+ " TACACS+ server");
+ ret = 0;
+ break;
+ }
+ /* continue the while loop; go read tac response */
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_RESTART:
+ /* try it again */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_RESTART (not impl)");
+
+ /*
+ * not implemented
+ * WdJ: I *think* you can just do tac_authen_send(user, pass) again
+ * but I'm not sure
+ */
+ ret = 0;
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_ERROR:
+ /* server has problems */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_ERROR");
+
+ ret = 0;
+ break;
+
+ case TAC_PLUS_AUTHEN_STATUS_FOLLOW:
+ /* server tells to try a different server address */
+ /* not implemented */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status:"
+ " TAC_PLUS_AUTHEN_STATUS_FOLLOW");
+
+ ret = 0;
+ break;
+
+ default:
+ if (msg < 0) {
+ /* connection error */
+ ret = 0;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "error communicating with"
+ " tacacs server");
+ break;
+ }
+
+ /* unknown response code */
+ ret = 0;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "tacacs status: unknown response"
+ " 0x%02x", msg);
+ }
+
+ if (NULL != resp) {
+ free(resp->resp);
+ free(resp);
+ }
+
+ if (NULL != re.msg);
+ free(re.msg);
+
+ return ret;
+}
+
+/*
+ * Only acct and auth now; should handle all the cases here
+ * Talk to the tacacs server for each type of transaction conversation
+ */
+static void talk_tac_server(int ctrl, int fd, char *user, char *pass,
+ char *tty, char *r_addr, struct tac_attrib **attr,
+ int *sptr, struct areply *reply,
+ pam_handle_t * pamh) {
+ if (!pass) { /* accounting */
+ int retval;
+ struct areply arep;
+ if (attr)
+ retval = tac_author_send(fd, user, tty, r_addr, *attr);
+ if(!attr || retval < 0) {
+ pam_syslog(pamh, LOG_ERR, "error getting authorization");
+ *sptr = PAM_AUTHINFO_UNAVAIL;
+ return;
+ }
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: sent authorization request for"
+ " [%s]", __func__, user);
+
+ arep.msg = NULL;
+ tac_author_read(fd, &arep);
+ if (reply)
+ *reply = arep;
+
+ if(arep.status != AUTHOR_STATUS_PASS_ADD &&
+ arep.status != AUTHOR_STATUS_PASS_REPL) {
+ /*
+ * if !pass, we are validating that the user is known, and
+ * these status values usually means the server does not know
+ * about the user so set USER_UNKNOWN, so we go on to the next
+ * pam module. This is consistent with libnss_tacplus.
+ *
+ * If pass, then this will usually be authorization denied due
+ * to one of the permission checks.
+ */
+ if (!pass) {
+ *sptr = PAM_USER_UNKNOWN;
+ /* do any logging at caller, since this is debug */
+ }
+ else {
+ *sptr = PAM_PERM_DENIED;
+ pam_syslog(pamh, LOG_WARNING, "TACACS+ authorization failed"
+ " for [%s] (tacacs status=%d)", user, arep.status);
+ }
+ if(arep.msg != NULL && !reply)
+ free (arep.msg); /* if reply is set, caller will free */
+ return;
+ }
+ else {
+ *sptr = PAM_SUCCESS;
+ if(arep.msg && !reply)
+ free (arep.msg); /* caller will free if reply is set */
+ }
+ }
+ else { /* authentication */
+ if (tac_authen_send(fd, user, pass, tty, r_addr, TAC_PLUS_AUTHEN_LOGIN)
+ < 0) {
+ pam_syslog(pamh, LOG_ERR, "error sending auth req to TACACS+"
+ " server");
+ }
+ else {
+ while ( tac_auth_converse(ctrl, fd, sptr, pass, pamh))
+ ;
+ }
+ }
+}
+
+
+/*
+ * find a responding tacacs server, and converse with it.
+ * See comments at do_tac_connect() below
+ */
+static void find_tac_server(int ctrl, int *tacfd, char *user, char *pass,
+ char *tty, char *r_addr, struct tac_attrib **attr,
+ int *sptr, struct areply *reply,
+ pam_handle_t * pamh) {
+ int fd = -1, srv_i;
+
+ tac_chk_anyresp();
+ for (srv_i = 0; srv_i < tac_srv_no; srv_i++) {
+ fd = -1;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: trying srv[%d] %s", __func__,
+ srv_i, tac_srv[srv_i].addr ?
+ tac_ntop(tac_srv[srv_i].addr->ai_addr) : "not set");
+
+ /*
+ * Try using our most recent server, if we had one. This works for all
+ * but accounting, where we should start from beginning of list.
+ */
+ if (active_server.addr) {
+ fd = tac_connect_single(active_server.addr, active_server.key,
+ tac_src_addr_info, __vrfname);
+ if (fd < 0)
+ active_server.addr = NULL; /* start from beginning */
+ }
+ if (fd < 0) {
+ if (tac_srv[srv_i].not_resp)
+ continue;
+ /* no active_server, or it failed, and curr not failed */
+ fd = tac_connect_single(tac_srv[srv_i].addr, tac_srv[srv_i].key,
+ tac_src_addr_info, __vrfname);
+ }
+ if (fd < 0) {
+ pam_syslog(pamh, LOG_ERR, "connection to srv[%d] %s failed: %m",
+ srv_i, tac_srv[srv_i].addr ?
+ tac_ntop(tac_srv[srv_i].addr->ai_addr) : "not set");
+ active_server.addr = NULL;
+ continue;
+ }
+
+ talk_tac_server(ctrl, fd, user, pass, tty, r_addr, attr, sptr,
+ reply, pamh);
+
+ if (*sptr == PAM_SUCCESS || *sptr == PAM_AUTH_ERR ||
+ *sptr == PAM_USER_UNKNOWN) {
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: srv[%d] %s, pam_status=%d",
+ __func__, srv_i, tac_ntop(tac_srv[srv_i].addr->ai_addr),
+ *sptr);
+ if (*sptr == PAM_SUCCESS) {
+ if (active_server.addr == NULL) {
+ active_server.addr = tac_srv[srv_i].addr;
+ active_server.key = tac_srv[srv_i].key;
+ }
+ break;
+ }
+ /* else try other servers, if any. On errs, won't need fd */
+ }
+ if (active_server.addr) {
+ /*
+ * We connected, but got a failure, don't re-use, start
+ * over on next pass or connection attempt
+ */
+ active_server.addr = NULL;
+ }
+ close(fd);
+ fd = -1;
+ }
+ *tacfd = fd;
+}
+
+/*
+ * We have to make a new connection each time, because libtac is single
+ * threaded (doesn't support multiple connects at the same time due to
+ * use of globals), and doesn't have support for persistent connections.
+ * That's fixable, but not worth the effort at this point.
+ *
+ * Trying to make this common code is ugly, but worth it to simplify
+ * maintenance and debugging.
+ *
+ * The problem is that the definition allows for multiple tacacs
+ * servers to be consulted, but a lot of the code was written such
+ * that once a server is found that responds, it keeps using it.
+ * That means when we are finding a server we need to do the full sequence.
+ * The related issue is that the lower level code can't communicate
+ * with multiple servers at the same time, and can't keep a connection
+ * open.
+ *
+ * TODO: Really should have a structure to pass user, pass, tty, and r_addr
+ * around everywhere.
+ */
+static int do_tac_connect(int ctrl, char *user, char *pass,
+ char *tty, char *r_addr, struct tac_attrib **attr,
+ struct areply *reply, pam_handle_t * pamh) {
+ int status = PAM_AUTHINFO_UNAVAIL, fd;
+
+ if (active_server.addr == NULL) { /* find a server with the info we want */
+ find_tac_server(ctrl, &fd, user, pass, tty, r_addr, attr, &status,
+ reply, pamh);
+ }
+ else { /* connect to the already chosen server, so we get
+ * consistent results. */
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: use previous server %s", __func__,
+ tac_ntop(active_server.addr->ai_addr));
+
+ fd = tac_connect_single(active_server.addr, active_server.key,
+ tac_src_addr_info, __vrfname);
+ if (fd < 0)
+ pam_syslog(pamh, LOG_ERR, "reconnect failed to %s: %m",
+ tac_ntop(active_server.addr->ai_addr));
+ else
+ talk_tac_server(ctrl, fd, user, pass, tty, r_addr, attr, &status,
+ reply, pamh);
+ }
+
+ /*
+ * this is debug because we can get called for any user for
+ * commands like sudo, not just tacacs users, so it's not an
+ * error to fail here. The caller can handle the logging.
+ */
+ if ((ctrl & PAM_TAC_DEBUG) && status != PAM_SUCCESS &&
+ status != PAM_USER_UNKNOWN)
+ pam_syslog(pamh, LOG_DEBUG, "no more servers to connect");
+ if (fd != -1)
+ close(fd); /* acct caller doesn't need connection */
+ return status;
+}
+
+/* Main PAM functions */
+
+/* authenticates user on remote TACACS+ server
+ * returns PAM_SUCCESS if the supplied username and password
+ * pair is valid
+ * First check to see if the user is known to the server(s),
+ * so we don't go through the password phase for unknown
+ * users, and can return PAM_USER_UNKNOWN to the process.
+ */
+PAM_EXTERN
+int pam_sm_authenticate (pam_handle_t * pamh, int flags,
+ int argc, const char **argv) {
+ int ctrl, retval;
+ char *user, *puser;
+ char *pass;
+ char *tty;
+ char *r_addr;
+ int status;
+ struct tac_attrib **attr, *attr_s = NULL;
+
+ priv_level = 0;
+ user = pass = tty = r_addr = NULL;
+
+ ctrl = _pam_parse(pamh, argc, argv);
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)",
+ __func__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT);
+
+ /* reset static state in case we are re-entered */
+ _reset_saved_user(pamh, ctrl & PAM_TAC_DEBUG);
+
+ /*
+ * If a mapped user entry already exists, we are probably being
+ * used for su or sudo, so we need to get the original user password,
+ * rather than the mapped user.
+ * Decided based on auid != uid and then do the lookup, similar to
+ * find_pw_user() in nss_tacplusc
+ */
+ _pam_get_user(pamh, &puser);
+ user = get_user_to_auth(puser);
+ if (user == NULL)
+ return PAM_USER_UNKNOWN;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: user [%s] obtained", __func__, user);
+
+ _pam_get_terminal(pamh, &tty);
+ if (!strncmp(tty, "/dev/", 5))
+ tty += 5;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: tty [%s] obtained", __func__, tty);
+
+ _pam_get_rhost(pamh, &r_addr);
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: rhost [%s] obtained", __func__,
+ r_addr);
+
+ /*
+ * Since these attributes are just for validating that the user is known to
+ * at least one server, it doesn't really matter whether these are "correct"
+ */
+ attr = &attr_s;
+ tac_add_attrib(attr, "service", tac_service?tac_service:"shell");
+ tac_add_attrib(attr, "protocol", tac_protocol?tac_protocol:"ssh");
+ tac_add_attrib(attr, "cmd", "");
+
+ status = do_tac_connect(ctrl, user, NULL, tty, r_addr, &attr_s, NULL, pamh);
+ tac_free_attrib(&attr_s);
+ if (status != PAM_SUCCESS) {
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "TACACS+ user [%s] unknown,"
+ " (pam status=%d)", user, status);
+ goto err;
+ }
+
+ retval = tacacs_get_password (pamh, flags, ctrl, &pass);
+ if (retval != PAM_SUCCESS || pass == NULL || *pass == '\0') {
+ pam_syslog(pamh, LOG_ERR, "unable to obtain password");
+ status = PAM_CRED_INSUFFICIENT;
+ goto err;
+ }
+
+ retval = pam_set_item (pamh, PAM_AUTHTOK, pass);
+ if (retval != PAM_SUCCESS) {
+ pam_syslog(pamh, LOG_ERR, "unable to set password");
+ status = PAM_CRED_INSUFFICIENT;
+ goto err;
+ }
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: password obtained", __func__);
+
+ status = do_tac_connect(ctrl, user, pass, tty, r_addr, NULL, NULL, pamh);
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: exit with pam status: %d", __func__,
+ status);
+
+err:
+ if (user && user != puser)
+ free(user); /* it was stdrup'ed */
+ if (NULL != pass) {
+ bzero(pass, strlen (pass));
+ free(pass);
+ }
+
+ return status;
+} /* pam_sm_authenticate */
+
+
+/* no-op function to satisfy PAM authentication module */
+PAM_EXTERN
+int pam_sm_setcred (pam_handle_t * pamh, int flags,
+ int argc, const char **argv) {
+
+ int ctrl = _pam_parse (pamh, argc, argv);
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)",
+ __func__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT);
+
+ return PAM_SUCCESS;
+} /* pam_sm_setcred */
+
+
+/* authorizes user on remote TACACS+ server, i.e. checks
+ * his permission to access requested service
+ * returns PAM_SUCCESS if the service is allowed
+ */
+PAM_EXTERN
+int pam_sm_acct_mgmt (pam_handle_t * pamh, int flags,
+ int argc, const char **argv) {
+
+ int ctrl, status=PAM_AUTH_ERR;
+ char *user;
+ char *tty;
+ char *r_addr;
+ struct areply arep;
+ struct tac_attrib *attr_s = NULL, *attr;
+
+ user = tty = r_addr = NULL;
+ memset(&arep, 0, sizeof(arep));
+
+ /* this also obtains service name for authorization
+ this should be normally performed by pam_get_item(PAM_SERVICE)
+ but since PAM service names are incompatible TACACS+
+ we have to pass it via command line argument until a better
+ solution is found ;) */
+ ctrl = _pam_parse (pamh, argc, argv);
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)",
+ __func__, PAM_TAC_VMAJ, PAM_TAC_VMIN, PAM_TAC_VPAT);
+
+ _pam_get_user(pamh, &user);
+ if (user == NULL)
+ return PAM_USER_UNKNOWN;
+
+
+ _pam_get_terminal(pamh, &tty);
+ if(!strncmp(tty, "/dev/", 5))
+ tty += 5;
+
+ _pam_get_rhost(pamh, &r_addr);
+
+ /* checks for specific data required by TACACS+, which should
+ be supplied in pam module command line */
+ if(!*tac_service) {
+ pam_syslog(pamh, LOG_ERR, "TACACS+ service type not configured");
+ return PAM_AUTHINFO_UNAVAIL;
+ }
+ tac_add_attrib(&attr_s, "service", tac_service);
+
+ if(tac_protocol != NULL && tac_protocol[0] != '\0')
+ tac_add_attrib(&attr_s, "protocol", tac_protocol);
+ else
+ pam_syslog(pamh, LOG_ERR, "TACACS+ protocol type not configured"
+ " (IGNORED)");
+
+ tac_add_attrib(&attr_s, "cmd", "");
+
+ memset(&arep, 0, sizeof arep);
+
+ /*
+ * Check if user is authorized, independently of authentication.
+ * Authentication may have happened via ssh public key, rather than
+ * via TACACS+. PAM should not normally get to this entry point if
+ * user is not yet authenticated.
+ * We only write the mapping entry (if needed) when authorization
+ * is succesful.
+ * attr is not used here, but having a non-NULL value is how
+ * talk_tac_server() distinguishes that it is an acct call, vs auth
+ * TODO: use a different mechanism
+ */
+ status = do_tac_connect(ctrl, user, NULL, tty, r_addr, &attr_s,
+ &arep, pamh);
+ tac_free_attrib(&attr_s);
+ if(active_server.addr == NULL) {
+ /* we need to return PAM_AUTHINFO_UNAVAIL here, rather than
+ * PAM_AUTH_ERR, or we can't use "ignore" or auth_err=bad in the
+ * pam configuration
+ */
+ status = PAM_AUTHINFO_UNAVAIL;
+ goto cleanup;
+ }
+
+ if(status) {
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_NOTICE, "No TACACS mapping for %s after auth"
+ " failure", user);
+ goto cleanup;
+ }
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: user [%s] successfully authorized",
+ __func__, user);
+
+ attr = arep.attr;
+ while (attr != NULL) {
+ const int new_len = attr->attr_len+1; /* 1 longer for terminating 0 */
+ char attribute[new_len];
+ char value[new_len];
+ char attrenv[new_len];
+ char tmpstr[new_len];
+ char *sep;
+
+ snprintf(tmpstr, sizeof tmpstr, "%*s", attr->attr_len,attr->attr);
+ sep = index(tmpstr, '=');
+ if(sep == NULL)
+ sep = index(tmpstr, '*');
+ if(sep != NULL) {
+ *sep = '\0';
+ snprintf(attribute, sizeof attribute, "%s", tmpstr);
+ snprintf(value, sizeof value, "%s", ++sep);
+
+ size_t i;
+ for (i = 0; attribute[i] != '\0'; i++) {
+ attribute[i] = toupper(attribute[i]);
+ if (attribute[i] == '-')
+ attribute[i] = '_';
+ }
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: returned attribute `%s(%s)'"
+ " from server", __func__, attribute, value);
+
+ if(strncmp(attribute, "PRIV", 4) == 0) {
+ char *ok;
+
+ priv_level = (unsigned)strtoul(value, &ok, 0);
+ /* if this fails, we leave priv_level at 0, which is
+ * least privileged, so that's OK, but at least report it
+ */
+ if (ok == value)
+ pam_syslog(pamh, LOG_WARNING,
+ "%s: non-numeric privilege for %s, got (%s)",
+ __func__, attribute, value);
+ }
+
+ /*
+ * make returned attributes available for other PAM modules via PAM
+ * environment. Since separator can be = or *, ensure it's = for
+ * the env.
+ */
+ snprintf(attrenv, sizeof attrenv, "%s=%s", attribute, value);
+ if (pam_putenv(pamh, attrenv) != PAM_SUCCESS)
+ pam_syslog(pamh, LOG_WARNING, "%s: unable to set PAM"
+ " environment (%s)", __func__, attribute);
+
+ } else {
+ pam_syslog(pamh, LOG_WARNING, "%s: invalid attribute `%s',"
+ " no separator", __func__, attr->attr);
+ }
+ attr = attr->next;
+ }
+
+ update_mapped(pamh, user, priv_level, r_addr);
+
+
+cleanup:
+ /* free returned attributes */
+ if(arep.attr != NULL)
+ tac_free_attrib(&arep.attr);
+
+ if(arep.msg != NULL)
+ free (arep.msg);
+
+ return status;
+} /* pam_sm_acct_mgmt */
+
+/*
+ * accounting packets may be directed to any TACACS+ server,
+ * independent from those used for authentication and authorization;
+ * they may be also directed to all specified servers
+ */
+
+static short unsigned int session_taskid;
+
+/*
+ * send START accounting request to the remote TACACS+ server
+ * returns PAM error only if the request was refused or there
+ * were problems connection to the server
+ * sets sess_taskid so it can be used in close_session, so that
+ * accounting start and stop records have the same task_id, as
+ * the specification requires.
+ */
+PAM_EXTERN
+int pam_sm_open_session (pam_handle_t * pamh, int flags,
+ int argc, const char **argv) {
+
+ if (!task_id)
+#if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO)
+ RAND_pseudo_bytes((unsigned char *) &task_id, sizeof(task_id));
+#else
+ task_id=(short int) tac_magic();
+#endif
+ session_taskid = task_id;
+ return _pam_account(pamh, argc, argv, TAC_PLUS_ACCT_FLAG_START, NULL);
+} /* pam_sm_open_session */
+
+/* sends STOP accounting request to the remote TACACS+ server
+ * returns PAM error only if the request was refused or there
+ * were problems connection to the server
+ */
+PAM_EXTERN
+int pam_sm_close_session (pam_handle_t * pamh, int flags,
+ int argc, const char **argv) {
+ int rc;
+ char *user;
+
+ _pam_get_user(pamh, &user);
+
+ task_id = session_taskid; /* task_id must match start */
+ rc = _pam_account(pamh, argc, argv, TAC_PLUS_ACCT_FLAG_STOP, NULL);
+ __update_loguid(user); /* now dead, cleanup mapping */
+ return rc;
+} /* pam_sm_close_session */
+
+
+#ifdef PAM_SM_PASSWORD
+/* Tested for servers that require password change during challenge/response */
+PAM_EXTERN
+int pam_sm_chauthtok(pam_handle_t * pamh, int flags,
+ int argc, const char **argv) {
+
+ int ctrl;
+ char *user;
+ char *pass;
+ char *tty;
+ char *r_addr;
+ const void *pam_pass = NULL;
+ int status;
+
+ user = pass = tty = r_addr = NULL;
+
+ ctrl = _pam_parse(pamh, argc, argv);
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: called (pam_tacplus v%u.%u.%u)"
+ " (flags=%d, argc=%d)", __func__, PAM_TAC_VMAJ, PAM_TAC_VMIN,
+ PAM_TAC_VPAT, flags, argc);
+
+ if ( (pam_get_item(pamh, PAM_OLDAUTHTOK, &pam_pass) == PAM_SUCCESS)
+ && (pam_pass != NULL) ) {
+ if ((pass = strdup(pam_pass)) == NULL)
+ return PAM_BUF_ERR;
+ } else {
+ pass = strdup("");
+ }
+
+ _pam_get_user(pamh, &user);
+ if (user == NULL) {
+ if(pass) {
+ free(pass);
+ }
+ return PAM_USER_UNKNOWN;
+ }
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: user [%s] obtained", __func__, user);
+
+ _pam_get_terminal(pamh, &tty);
+ if (tty && !strncmp(tty, "/dev/", 5))
+ tty += 5;
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: tty [%s] obtained", __func__,
+ tty?tty:"UNKNOWN");
+
+ _pam_get_rhost(pamh, &r_addr);
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: rhost [%s] obtained", __func__,
+ r_addr?r_addr:"UNKNOWN");
+
+ if (PAM_SILENT != (flags & PAM_SILENT))
+ status = do_tac_connect(ctrl, user, pass, tty, r_addr, NULL, NULL,
+ pamh);
+ else
+ status = PAM_AUTHTOK_ERR;
+
+ if (status != PAM_SUCCESS && status != PAM_AUTHTOK_ERR)
+ pam_syslog(pamh, LOG_ERR, "no more servers to connect");
+
+ if (ctrl & PAM_TAC_DEBUG)
+ pam_syslog(pamh, LOG_DEBUG, "%s: exit with pam status: %d", __func__,
+ status);
+
+ if (NULL != pass) {
+ bzero(pass, strlen(pass));
+ free(pass);
+ pass = NULL;
+ }
+
+ return status;
+
+} /* pam_sm_chauthtok */
+#endif
+
+
+#ifdef PAM_STATIC
+struct pam_module _pam_tacplus_modstruct {
+ "pam_tacplus",
+ pam_sm_authenticate,
+ pam_sm_setcred,
+ pam_sm_acct_mgmt,
+ pam_sm_open_session,
+ pam_sm_close_session,
+#ifdef PAM_SM_PASSWORD
+ pam_sm_chauthtok
+#else
+ NULL
+#endif
+};
+#endif
diff --git a/pam_tacplus.h b/pam_tacplus.h
new file mode 100644
index 0000000..76dd69f
--- /dev/null
+++ b/pam_tacplus.h
@@ -0,0 +1,50 @@
+/* pam_tacplus.h
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef PAM_TACPLUS_H
+#define PAM_TACPLUS_H
+
+/* define these before including PAM headers */
+#define PAM_SM_AUTH
+#define PAM_SM_ACCOUNT
+#define PAM_SM_SESSION
+#define PAM_SM_PASSWORD
+
+#include <security/pam_appl.h>
+#include <security/pam_modules.h>
+
+/* pam_tacplus command line options */
+#define PAM_TAC_DEBUG 0x01
+#define PAM_TAC_ACCT 0x02 /* account on all specified servers */
+#define PAM_TAC_USE_FIRST_PASS 0x04
+#define PAM_TAC_TRY_FIRST_PASS 0x08
+
+/* major, minor and patchlevel version numbers; should match pkg version */
+#define PAM_TAC_VMAJ 1
+#define PAM_TAC_VMIN 4
+#define PAM_TAC_VPAT 3
+
+#ifndef PAM_EXTERN
+ #define PAM_EXTERN extern
+#endif
+
+#endif /* PAM_TACPLUS_H */
+
diff --git a/pam_tacplus.spec.in b/pam_tacplus.spec.in
new file mode 100644
index 0000000..f894992
--- /dev/null
+++ b/pam_tacplus.spec.in
@@ -0,0 +1,90 @@
+#
+# spec file for package 'name' (version 'v')
+#
+# The following software is released as specified below.
+# This spec file is released to the public domain.
+# (c) Lincom Software Team
+
+# Basic Information
+Name: pam_tacplus
+Version: @VERSION@
+Release: 1%{?dist}
+Summary: PAM Tacacs+ module
+Group: System
+License: GPL
+URL: http://tacplus.sourceforge.net/
+
+# Packager Information
+Packager: NRB
+
+# Build Information
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+# Source Information
+Source0: https://github.com/jeroennijhof/pam_tacplus/archive/@VERSION@.tar.gz
+
+# Dependency Information
+BuildRequires: gcc binutils pam-devel
+Requires: pam
+
+%description
+PAM Tacacs+ module based on code produced by Pawel Krawczyk <pawel.krawczyk@hush.com> and Jeroen Nijhof <jeroen@jeroennijhof.nl>
+
+%package devel
+Group: Development/Libraries
+Summary: Development files for pam_tacplus
+Requires: pam_tacplus
+
+%description devel
+Development files for pam_tacplus.
+
+%prep
+%setup -q -a 0
+
+%build
+autoreconf -i
+./configure
+make
+
+%install
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/etc/pam.d
+mkdir -p $RPM_BUILD_ROOT/%{_lib}/security
+
+install -m 755 .libs/pam_tacplus.so \
+ $RPM_BUILD_ROOT/%{_lib}/security/
+install -m 644 sample.pam $RPM_BUILD_ROOT/etc/pam.d/tacacs
+
+chmod 755 $RPM_BUILD_ROOT/%{_lib}/security/*.so*
+
+make install DESTDIR=$RPM_BUILD_ROOT
+chmod 755 $RPM_BUILD_ROOT/usr/local/include/libtac
+
+%clean
+[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root)
+%attr(0755,root,root) /%{_lib}/security/*.so
+%attr(0755,root,root) /usr/local/lib/*.so.*
+%attr(0644,root,root) %config(noreplace) /etc/pam.d/tacacs
+%doc AUTHORS COPYING README.md ChangeLog
+
+%files devel
+%defattr(-,root,root,-)
+%attr(755,root,root) /usr/local/bin/*
+%attr(644,root,root) /usr/local/include/*
+%attr(755,root,root) /usr/local/lib/*.so
+%attr(755,root,root) /usr/local/lib/*.la
+%attr(755,root,root) /usr/local/lib/security/*
+%attr(644,root,root) /usr/local/lib/pkgconfig/*
+%doc /usr/local/share/doc/*
+
+%changelog
+* Thu Feb 2 2012 - Jeroen <jeroen@jeroennijhof.nl>
+- Path changed for pam_tacplus.so
+- Not using static library path anymore
+
+* Mon Mar 17 2010 - beNDon <benoit.donneaux@gmail.com>
+- Autotools aware
+- spec file added for RPM building
diff --git a/sample.pam b/sample.pam
new file mode 100644
index 0000000..19fa187
--- /dev/null
+++ b/sample.pam
@@ -0,0 +1,21 @@
+#%PAM-1.0
+# The secret keyword must follow the server keyword.
+# is matched up with first secret keyword, and so on. There must be at least as
+# many secret keywords as there are keywords.
+# Servers are tried in the order listed, and for authorization (account), the
+# same tacacs+ server is used that was used for authentication. For tacacs+
+# accounting (session), without the acct_all keyword, the same tacacs+ server is
+# used. With acct_all, the accounting record is sent to all listed and
+# responding tacacs+ servers. See the README file in the source for more
+# details.
+# An alternative tp service=ppp protocol=lcp for account and session would be
+# login=login service=shell protocol=ssh
+# Common parameters can also be set in /etc/tacplus_servers, rather than
+# the commandline by using the include=/etc/tacplus_servers paramter.
+# For the secret parameter, this also improves security
+auth required /lib/security/pam_tacplus.so debug server=1.1.1.1 server=2.2.2.2:49 secret=SAME-SECRET
+account required /lib/security/pam_tacplus.so debug service=ppp protocol=lcp
+account sufficient /lib/security/pam_exec.so /usr/local/bin/showenv.sh
+password required /lib/security/pam_cracklib.
+password required /lib/security/pam_pwdb.so shadow use_authtok
+session required /lib/security/pam_tacplus.so debug server=1.1.1.1 secret=SECRET-1 server=2.2.2.2:49 secret=SECRET-2 service=ppp protocol=lcp
diff --git a/support.c b/support.c
new file mode 100644
index 0000000..54d8124
--- /dev/null
+++ b/support.c
@@ -0,0 +1,649 @@
+/* support.c - support functions for pam_tacplus.c
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ * Copyright 2016, 2017, 2018 Cumulus Networks, Inc. All rights reserved.
+ *
+ * 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 - see the file COPYING.
+ */
+
+#define PAM_SM_AUTH
+#define PAM_SM_ACCOUNT
+#define PAM_SM_SESSION
+#define PAM_SM_PASSWORD
+
+#include "support.h"
+#include "pam_tacplus.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS];
+extern tacplus_server_t active_server;
+int tac_srv_no = 0;
+static int tac_key_no;
+static int debug; /* so we don't need to get from pam */
+static int printed_servers; /* only debug server list once */
+
+char tac_service[64];
+char tac_protocol[64];
+char tac_prompt[64];
+char *__vrfname;
+struct sockaddr src_sockaddr;
+struct addrinfo src_addr_info;
+struct addrinfo *tac_src_addr_info;
+unsigned tac_use_tachome;
+
+#define MAX_INCL 8 /* max config level nesting */
+
+#include <utmpx.h>
+/* original name passed in via PAM; this makes this library not usable for
+ * multiple calls for different users, but that should be OK for PAM. That's
+ * handled by calling _reset_saved_user from pam_sm_open_session()
+ */
+static char orig_user[__UT_NAMESIZE];
+
+/* used when we have a persistent connection */
+void _reset_saved_user(pam_handle_t *pamh, int debug)
+{
+ if (*orig_user && debug)
+ pam_syslog(pamh, LOG_DEBUG, "re-entered, clearing saved userid=%s",
+ orig_user);
+ *orig_user = 0;
+}
+
+/* These functions return static info, overwritten on subsequent calls.
+ * Since orig_user is static global, we aren't multi-threaded, but can
+ * handle multiple users, as log as pam_sm_open_session is called for
+ * each, because we'll call _reset_saved_user() to clear.
+ */
+void _pam_get_user(pam_handle_t *pamh, char **user) {
+ int retval;
+
+ if (!user)
+ return;
+
+ if (*orig_user) {
+ *user = orig_user; /* never our modified user */
+ return;
+ }
+ retval = pam_get_user(pamh, (void *)user, "Username: ");
+ if (retval != PAM_SUCCESS || *user == NULL || **user == '\0') {
+ pam_syslog(pamh, LOG_ERR, "unable to obtain username");
+ *user = NULL;
+ }
+ else
+ strncpy(orig_user, *user, sizeof (orig_user)-1);
+}
+
+
+/* These functions return static info, overwritten on subsequent calls. */
+void _pam_get_terminal(pam_handle_t *pamh, char **tty) {
+ int retval;
+
+ if (!tty)
+ return;
+
+ retval = pam_get_item(pamh, PAM_TTY, (void *)tty);
+ if (retval != PAM_SUCCESS || *tty == NULL || **tty == '\0') {
+ *tty = ttyname(STDIN_FILENO);
+ if(*tty == NULL || **tty == '\0')
+ *tty = "unknown";
+ }
+}
+
+/* These functions return static info, overwritten on subsequent calls. */
+void _pam_get_rhost(pam_handle_t *pamh, char **rhost) {
+ int retval;
+
+ if (!rhost)
+ return;
+
+ retval = pam_get_item(pamh, PAM_RHOST, (void *)rhost);
+ if (retval != PAM_SUCCESS || *rhost == NULL || **rhost == '\0') {
+ *rhost = "unknown";
+ }
+}
+
+int converse(pam_handle_t * pamh, int nargs, const struct pam_message *message,
+ struct pam_response **response) {
+
+ int retval;
+ struct pam_conv *conv;
+
+ if ((retval = pam_get_item (pamh, PAM_CONV, (const void **)&conv)) ==
+ PAM_SUCCESS) {
+ retval = conv->conv(nargs, &message, response, conv->appdata_ptr);
+
+ if (retval != PAM_SUCCESS)
+ pam_syslog(pamh, LOG_ERR, "converse returned %d"
+ "that is: %s", retval, pam_strerror (pamh, retval));
+ } else {
+ pam_syslog(pamh, LOG_ERR, "converse failed to get pam_conv");
+ }
+
+ return retval;
+}
+
+/* stolen from pam_stress */
+int tacacs_get_password (pam_handle_t * pamh, int flags
+ ,int ctrl, char **password) {
+
+ const void *pam_pass;
+ char *pass = NULL;
+
+ if (ctrl & PAM_TAC_DEBUG)
+ syslog (LOG_DEBUG, "%s: called", __func__);
+
+ if ( (ctrl & (PAM_TAC_TRY_FIRST_PASS | PAM_TAC_USE_FIRST_PASS))
+ && (pam_get_item(pamh, PAM_AUTHTOK, &pam_pass) == PAM_SUCCESS)
+ && (pam_pass != NULL) ) {
+ if ((pass = strdup(pam_pass)) == NULL)
+ return PAM_BUF_ERR;
+ } else if ((ctrl & PAM_TAC_USE_FIRST_PASS)) {
+ pam_syslog(pamh, LOG_WARNING, "no forwarded password");
+ return PAM_PERM_DENIED;
+ } else {
+ struct pam_message msg;
+ struct pam_response *resp = NULL;
+ int retval;
+
+ /* set up conversation call */
+ msg.msg_style = PAM_PROMPT_ECHO_OFF;
+
+ if (!tac_prompt[0]) {
+ msg.msg = "Password: ";
+ } else {
+ msg.msg = tac_prompt;
+ }
+
+ if ((retval = converse (pamh, 1, &msg, &resp)) != PAM_SUCCESS)
+ return retval;
+
+ if (resp != NULL) {
+ if (resp->resp == NULL && (ctrl & PAM_TAC_DEBUG))
+ pam_syslog(pamh, LOG_DEBUG, "%s: NULL authtok given",
+ __func__);
+
+ pass = resp->resp; /* remember this! */
+ resp->resp = NULL;
+
+ free(resp);
+ resp = NULL;
+ } else {
+ if (ctrl & PAM_TAC_DEBUG) {
+ pam_syslog(pamh, LOG_DEBUG, "getting password, but NULL"
+ " returned!?");
+ }
+ return PAM_CONV_ERR;
+ }
+ }
+
+ /*
+ FIXME *password can still turn out as NULL
+ and it can't be free()d when it's NULL
+ */
+ *password = pass; /* this *MUST* be free()'d by this module */
+
+ if(ctrl & PAM_TAC_DEBUG)
+ syslog(LOG_DEBUG, "%s: obtained password", __func__);
+
+ return PAM_SUCCESS;
+}
+
+static void reset_config(void)
+{
+ int i;
+
+ for (i = 0; i < tac_key_no; i++) {
+ if (tac_srv[i].key)
+ free(tac_srv[i].key);
+ tac_srv[i].not_resp = 0;
+ }
+ memset(tac_srv, 0, sizeof(tacplus_server_t) * TAC_PLUS_MAXSERVERS);
+ active_server.addr = NULL; /* be sure no refs into freed mem */
+ tac_src_addr_info = NULL;
+ tac_key_no = 0;
+ tac_srv_no = 0;
+ printed_servers = 0;
+}
+
+/* Convert ip address string to address info.
+ * It returns 0 on success, or -1 otherwise
+ * It supports ipv4 only.
+ */
+int ip_addr_str_to_addr_info (const char *srcaddr, struct addrinfo *p_addr_info)
+{
+ struct sockaddr_in *s_in;
+
+ s_in = (struct sockaddr_in *)p_addr_info->ai_addr;
+ s_in->sin_family = AF_INET;
+ s_in->sin_addr.s_addr = INADDR_ANY;
+
+ if (inet_pton(AF_INET, srcaddr, &(s_in->sin_addr)) == 1) {
+ p_addr_info->ai_family = AF_INET;
+ p_addr_info->ai_addrlen = sizeof (struct sockaddr_in);
+ return 0;
+ }
+ return -1;
+}
+
+static int parse_argfile(pam_handle_t *, const char *, int);
+
+/*
+ * parse arguments, one at a time. Separate routine
+ * so we can have arguments in include files, and use
+ * common code.
+ */
+static int parse_arg(pam_handle_t *pamh, const char *arg, int top) {
+ int ctrl = 0;
+
+ if(!strncmp (arg, "include=", 8)) {
+ /*
+ * allow include files, useful for centralizing tacacs
+ * server IP address and secret.
+ */
+ if(arg[8]) /* else treat as empty config */
+ ctrl |= parse_argfile(pamh, arg + 8, top);
+ }
+ else if (!strcmp (arg, "debug")) { /* all */
+ ctrl |= PAM_TAC_DEBUG;
+ } else if (!strncmp (arg, "debug=", 6)) { /* allow debug=Digits also */
+ unsigned val = (unsigned)strtoul(arg+6, NULL, 0);
+ if (val)
+ ctrl |= PAM_TAC_DEBUG;
+ } else if (!strcmp (arg, "use_first_pass")) {
+ ctrl |= PAM_TAC_USE_FIRST_PASS;
+ } else if (!strcmp (arg, "try_first_pass")) {
+ ctrl |= PAM_TAC_TRY_FIRST_PASS;
+ } else if (!strncmp (arg, "service=", 8)) { /* author & acct */
+ tac_xstrcpy (tac_service, arg + 8, sizeof(tac_service));
+ } else if (!strncmp (arg, "protocol=", 9)) { /* author & acct */
+ tac_xstrcpy (tac_protocol, arg + 9, sizeof(tac_protocol));
+ } else if (!strncmp (arg, "prompt=", 7)) { /* authentication */
+ tac_xstrcpy (tac_prompt, arg + 7, sizeof(tac_prompt));
+ /* Replace _ with space */
+ int chr;
+ for (chr = 0; chr < strlen(tac_prompt); chr++) {
+ if (tac_prompt[chr] == '_') {
+ tac_prompt[chr] = ' ';
+ }
+ }
+ } else if (!strncmp (arg, "login=", 6)) {
+ tac_xstrcpy (tac_login, arg + 6, sizeof(tac_login));
+ } else if (!strncmp (arg, "user_homedir=", 13)) {
+ tac_use_tachome = strtoul(arg+13, NULL, 0);
+ } else if (!strcmp (arg, "acct_all")) {
+ ctrl |= PAM_TAC_ACCT;
+ } else if (!strncmp (arg, "server=", 7)) { /* authen & acct */
+ if(tac_srv_no < TAC_PLUS_MAXSERVERS) {
+ struct addrinfo hints, *servers, *server;
+ int rv;
+ char *close_bracket, *server_name, *port, server_buf[256];
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC; /* use IPv4 or IPv6, whichever */
+ hints.ai_socktype = SOCK_STREAM;
+
+ if (strlen(arg + 7) >= sizeof(server_buf)) {
+ pam_syslog(pamh, LOG_ERR, "server address too long, sorry");
+ goto done;
+ }
+ strcpy(server_buf, arg + 7);
+
+ if (*server_buf == '[' &&
+ (close_bracket = strchr(server_buf, ']')) != NULL) {
+ /* Check for URI syntax */
+ server_name = server_buf + 1;
+ port = strchr(close_bracket, ':');
+ *close_bracket = '\0';
+ } else { /* Fall back to traditional syntax */
+ server_name = server_buf;
+ port = strchr(server_buf, ':');
+ }
+ if (port != NULL) {
+ *port = '\0';
+ port++;
+ }
+ if ((rv = getaddrinfo(server_name, (port == NULL) ? "49" : port,
+ &hints, &servers)) == 0) {
+ for(server = servers; server != NULL &&
+ tac_srv_no < TAC_PLUS_MAXSERVERS;
+ server = server->ai_next) {
+ tac_srv[tac_srv_no].addr = server;
+ /* use current key, if our index not yet set */
+ if(tac_key_no && !tac_srv[tac_srv_no].key)
+ tac_srv[tac_srv_no].key =
+ tac_xstrdup(tac_srv[tac_key_no-1].key);
+ tac_srv_no++;
+ }
+ } else {
+ pam_syslog(pamh, LOG_ERR,
+ "skip invalid server: %s (getaddrinfo: %s)",
+ server_name, gai_strerror(rv));
+ }
+ } else {
+ pam_syslog(pamh, LOG_ERR, "maximum number of servers (%d) exceeded,"
+ " skipping", TAC_PLUS_MAXSERVERS);
+ }
+ } else if (!strncmp (arg, "secret=", 7)) {
+ int i;
+ /* no need to complain if too many on this one */
+ if(tac_key_no < TAC_PLUS_MAXSERVERS) {
+ if((tac_srv[tac_key_no].key = tac_xstrdup(arg+7)))
+ tac_key_no++;
+ else
+ pam_syslog(pamh, LOG_ERR, "unable to copy server secret"
+ " %d: %m", tac_key_no);
+ }
+
+ /* if 'secret=' was given after a 'server=' parameter,
+ * fill in any unset keys up to current server number. */
+ for(i = tac_srv_no-1; i >= 0; i--) {
+ if (tac_srv[i].key)
+ continue;
+
+ tac_srv[i].key = tac_xstrdup(arg + 7);
+ }
+ } else if (!strncmp (arg, "timeout=", 8)) {
+ char *argend;
+ int val = (unsigned)strtol(arg+8, &argend, 0);
+ if (argend != (arg+8) && val >= 0) {
+ tac_timeout = val;
+ tac_readtimeout_enable = 1;
+ }
+ else
+ pam_syslog(pamh, LOG_WARNING, "invalid option value (%s)", arg);
+ } else if(!strncmp(arg, "vrf=", 4)) {
+ __vrfname = tac_xstrdup(arg + 4);
+ } else if (!strncmp (arg, "source_ip=", 10)) {
+ const char *srcip = arg + 10;
+ /* if source ip address, convert it to addr info */
+ memset (&src_addr_info, 0, sizeof (struct addrinfo));
+ memset (&src_sockaddr, 0, sizeof (struct sockaddr));
+ src_addr_info.ai_addr = &src_sockaddr;
+ if (ip_addr_str_to_addr_info (srcip, &src_addr_info) == 0)
+ tac_src_addr_info = &src_addr_info;
+ else {
+ tac_src_addr_info = NULL; /* for re-parsing or errors */
+ pam_syslog(pamh, LOG_WARNING,
+ "unable to convert %s to an IPv4 address", arg);
+ }
+ } else {
+ /*
+ * Don't complain about acct_all, we don't use it,
+ * but may be set for accounting code.
+ */
+ if(strncmp(arg, "acct_all=", 9))
+ pam_syslog(pamh, LOG_WARNING, "unrecognized option: %s", arg);
+ }
+done:
+ debug = ctrl & PAM_TAC_DEBUG;
+ return ctrl;
+}
+
+static int parse_argfile(pam_handle_t *pamh, const char *file, int top) {
+ FILE *conf;
+ char lbuf[256];
+ int ctrl = 0;
+ struct stat st, *lst;
+ static struct stat lastconf[MAX_INCL];
+ static char *filelist[MAX_INCL];
+ static int ctrl_list[MAX_INCL];
+ static int conf_parsed = 0;
+
+ if(top > MAX_INCL) {
+ pam_syslog(pamh, LOG_NOTICE, "Config file include depth > %d,"
+ " ignoring %s", MAX_INCL, file);
+ return 1;
+ }
+
+ lst = &lastconf[top-1];
+ if(conf_parsed && top == 1) {
+ /*
+ * Check to see if the config file(s) have changed since last time,
+ * If not, we don't want to re-parse, since the file parsing is
+ * invoked from each of the pam.d account, auth, session, etc. files
+ * This is somewhat complicated by the include file mechanism.
+ *
+ * Changes to config files while PAM is running will be rare,
+ * since a PAM session rarely runs more than a minute, except
+ * for the session cleanup at exit, which could be hours or days
+ *
+ * When we have nested includes, we have to check all the config
+ * files we saw previously, not just the top level config file.
+ * If no changes, don't reparse anything, but return the ctrl
+ * value from the previous parsing. If a change was required,
+ * reset the config values.
+ *
+ * That could include any server from earlier on the command line, but
+ * there is no other sane way to handle this, but at least it's
+ * predictable.
+ */
+ int i;
+ for(i=0; i < MAX_INCL; i++) {
+ struct stat *cst;
+ cst = &lastconf[i];
+ if(!cst->st_ino || !filelist[i]) { /* end of files */
+ return ctrl_list[top-1];
+ }
+ if (stat(filelist[i], &st) || st.st_ino != cst->st_ino ||
+ st.st_mtime != cst->st_mtime || st.st_ctime != cst->st_ctime)
+ break; /* found removed or different file, so re-parse */
+ }
+ reset_config();
+ }
+
+ /* don't check for failures, we'll just skip, don't want to error out */
+ filelist[top-1] = strdup(file);
+
+ conf = fopen(file, "r");
+ if(conf == NULL) {
+ pam_syslog(pamh, LOG_ERR, "Unable to open config file %s: %m", file);
+ return 0;
+ }
+
+ if (fstat(fileno(conf), lst) != 0)
+ memset(lst, 0, sizeof *lst); /* avoid stale data, no warning */
+
+ while (fgets(lbuf, sizeof lbuf, conf)) {
+ if(*lbuf == '#' || isspace(*lbuf))
+ continue; /* skip comments, white space lines, etc. */
+ strtok(lbuf, " \t\n\r\f"); /* terminate buffer at first whitespace */
+ ctrl |= parse_arg(pamh, lbuf, top + 1);
+ }
+ fclose(conf);
+ conf_parsed = 1;
+ ctrl_list[top-1] = ctrl;
+ return ctrl;
+}
+
+/*
+ * This has re-parse every time, because we can have different parameters
+ * For different pam.d files. We don't change configured variables (from
+ * earlier command lines, or config file) that aren't overridden on this
+ * command line.
+ */
+int _pam_parse (pam_handle_t *pamh, int argc, const char **argv) {
+ int i, ctrl = 0, reset_servers = 0;
+
+ /*
+ * Now that we have a config file, and don't have everything on the
+ * pam.d config file lines, we shouldn't clear any information here,
+ * we should only clear whatever is going to be (re)set.
+ *
+ * Because we can be called multiple times, we need to reset the state
+ * each time we go through this function for each of the args that are
+ * present on the command line. It turns out that the only ones that
+ * matter are related to the server.
+ * We need to free allocated memory to avoid memory leaks; for now,
+ * that's only the key.
+ *
+ * We are duplicating keyword parsing here to some degree, but it's
+ * limited, and this seems like the cleanest way to do it
+ *
+ * We make the limiting assumption that if server(s) are specified
+ * on the command line, that shared secrets will also be specified,
+ * and we clear the whole tac_srv array. That is, server and secret
+ * need to both be given on the command line, if either is given
+ *
+ * This also reduces timeouts when one or more servers (from the
+ * config file) are down, and we move from one pam type to another
+ * (session, account, auth).
+ */
+ for (i=0; i<argc && !reset_servers; i++) {
+ if (!strncmp(argv[i], "server=", 7) || !strncmp (argv[i], "secret=", 7))
+ reset_servers = 1;
+ }
+
+ if (reset_servers) {
+ reset_config();
+ }
+
+ for (ctrl = 0; argc-- > 0; ++argv)
+ ctrl |= parse_arg(pamh, *argv, 1);
+
+ if (ctrl & PAM_TAC_DEBUG) {
+ int n;
+
+ if (reset_servers)
+ pam_syslog(pamh, LOG_DEBUG, "%d servers defined on pam cmdline",
+ tac_srv_no);
+ else
+ pam_syslog(pamh, LOG_DEBUG, "%d servers defined", tac_srv_no);
+
+ if (!printed_servers) {
+ printed_servers = 1;
+ for(n = 0; n < tac_srv_no; n++) {
+ /* do not log the shared secret, it's a security issue */
+ pam_syslog(pamh, LOG_DEBUG, "server[%d] { addr=%s }",
+ n, tac_ntop(tac_srv[n].addr->ai_addr));
+ }
+
+ pam_syslog(pamh, LOG_DEBUG, "tac_service='%s' tac_protocol='%s'"
+ "tac_prompt='%s' tac_login='%s' source_ip='%s'",
+ tac_service, tac_protocol , tac_prompt, tac_login,
+ tac_src_addr_info ?
+ tac_ntop(tac_src_addr_info->ai_addr) : "unset");
+ }
+ }
+ return ctrl;
+} /* _pam_parse */
+
+
+/*
+ * when login is successful (from pam account entry point, after authorization
+ * succeeds), update our local mapping data, and if we are using the tacacs
+ * username in the home directory, create the home directory if needed (using
+ * the mkhomedir_helper program). The code to exec mkhomedir_helper is based on
+ * pam_mkhomedir.c
+ */
+void update_mapped(pam_handle_t *pamh, char *user, unsigned level, char *rhost)
+{
+ struct passwd *pw;
+ struct stat st;
+ int rc, retval, child, restore = 0;
+ struct sigaction newsa, oldsa;
+ const char *path = "/sbin/mkhomedir_helper";
+
+ if (!update_mapuser(user, level, rhost, tac_use_tachome))
+ return;
+
+ /*
+ * if we mapped the user name, set SUDO_PROMPT in env so that
+ * it prompts as the login user, not the mapped user, unless (unlikely)
+ * the prompt has already been set. Set SUDO_USER as well, for
+ * consistency.
+ */
+ if (!pam_getenv(pamh, "SUDO_PROMPT")) {
+ char nprompt[strlen("SUDO_PROMPT=[sudo] password for ") +
+ strlen(user) + 3]; /* + 3 for ": " and the \0 */
+ snprintf(nprompt, sizeof nprompt,
+ "SUDO_PROMPT=[sudo] password for %s: ", user);
+ if (pam_putenv(pamh, nprompt) != PAM_SUCCESS)
+ pam_syslog(pamh, LOG_NOTICE, "failed to set PAM sudo prompt (%s)",
+ nprompt);
+ }
+ if (!pam_getenv(pamh, "SUDO_USER")) {
+ char sudouser[strlen("SUDO_USER=") +
+ strlen(user) + 1]; /* + 1 for the \0 */
+ snprintf(sudouser, sizeof sudouser,
+ "SUDO_USER=%s", user);
+ if (pam_putenv(pamh, sudouser) != PAM_SUCCESS)
+ pam_syslog(pamh, LOG_NOTICE, "failed to set PAM sudo user (%s)",
+ sudouser);
+ }
+
+ if (!tac_use_tachome)
+ return;
+
+ pw = getpwnam(user); /* this should never fail, at this point... */
+ if (!pw) {
+ pam_syslog(pamh, LOG_NOTICE, "Unable to get passwd entry for user"
+ " (%s)", user);
+ return;
+ }
+
+ if (stat(pw->pw_dir, &st) == 0)
+ return;
+ if (debug)
+ pam_syslog(pamh, LOG_NOTICE, "creating home directory %s for user %s",
+ pw->pw_dir, user);
+
+ /*
+ * This code arranges that the demise of the child does not cause
+ * the application to receive a signal it is not expecting - which
+ * may kill the application or worse. Based on pam_mkhomedir.c
+ */
+ memset(&newsa, '\0', sizeof(newsa));
+ newsa.sa_handler = SIG_DFL;
+ if (sigaction(SIGCHLD, &newsa, &oldsa) == 0)
+ restore = 1;
+
+ child = fork();
+ if(child == -1) {
+ pam_syslog(pamh, LOG_ERR, "fork to exec %s %s failed: %m", path, user);
+ return;
+ }
+ if(child == 0) {
+ execl(path, path, user, NULL);
+ pam_syslog(pamh, LOG_ERR, "exec %s %s failed: %m", path, user);
+ exit(1);
+ }
+
+ while ((rc=waitpid(child, &retval, 0)) < 0 && errno == EINTR)
+ ;
+ if(rc < 0)
+ pam_syslog(pamh, LOG_ERR, "waitpid for exec of %s %s failed: %m", path,
+ user);
+ else if(!WIFEXITED(retval))
+ pam_syslog(pamh, LOG_ERR, "%s %s abnormal exit: 0x%x", path, user,
+ retval);
+ else {
+ retval = WEXITSTATUS(retval);
+ if(retval)
+ pam_syslog(pamh, LOG_ERR, "%s %s abnormal exit: %d", path, user,
+ retval);
+ }
+
+ if (restore)
+ sigaction(SIGCHLD, &oldsa, NULL);
+}
diff --git a/support.h b/support.h
new file mode 100644
index 0000000..920dc8d
--- /dev/null
+++ b/support.h
@@ -0,0 +1,62 @@
+/* support.h - support functions for pam_tacplus.c
+ *
+ * Copyright (C) 2010, Pawel Krawczyk <pawel.krawczyk@hush.com> and
+ * Jeroen Nijhof <jeroen@jeroennijhof.nl>
+ *
+ * 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 - see the file COPYING.
+ *
+ * See `CHANGES' file for revision history.
+ */
+
+#ifndef PAM_TACPLUS_SUPPORT_H
+#define PAM_TACPLUS_SUPPORT_H
+
+#include "libtac.h"
+
+#include <security/pam_modules.h>
+#include <security/pam_ext.h>
+
+#include <tacplus/map_tacplus_user.h>
+
+
+typedef struct {
+ struct addrinfo *addr;
+ char *key;
+ unsigned not_resp;
+} tacplus_server_t;
+
+extern tacplus_server_t tac_srv[TAC_PLUS_MAXSERVERS];
+extern int tac_srv_no;
+
+extern char tac_service[64];
+extern char tac_protocol[64];
+extern char tac_prompt[64];
+extern struct addrinfo *tac_src_addr_info;
+extern unsigned tac_use_tachome;
+
+int _pam_parse (pam_handle_t *, int, const char **);
+unsigned long _resolve_name (char *);
+unsigned long _getserveraddr (char *serv);
+int tacacs_get_password (pam_handle_t *, int, int, char **);
+int converse (pam_handle_t *, int, const struct pam_message *, struct pam_response **);
+void _pam_log (int, const char *, ...);
+void *_xcalloc (size_t);
+void _pam_get_user(pam_handle_t *, char **);
+void _pam_get_terminal(pam_handle_t *, char **);
+void _pam_get_rhost(pam_handle_t *, char **);
+void _reset_saved_user(pam_handle_t *, int);
+void update_mapped(pam_handle_t *, char *, unsigned, char *);
+
+#endif /* PAM_TACPLUS_SUPPORT_H */
+
diff --git a/tacc.1 b/tacc.1
new file mode 100644
index 0000000..7847d2c
--- /dev/null
+++ b/tacc.1
@@ -0,0 +1,146 @@
+.TH TACC 1
+.SH NAME
+tacc \- simple TACACS+ client and login program
+.SH SYNOPSIS
+.B tacc
+[-TRAVh] [-qwn] [-u \fIusername\fR] [-p \fIpassword\fR] [-s \fIserver\fR]
+[-k \fIsecret\fR] [-c \fI'command'\fR] [--username=\fIusername\fR]
+[--password=\fIpassword\fR] [--server=\fIserver\fR] [--secret=\fIsecret\fR]
+[--sourceip=\fI'IPv4_address'\fR]
+[--command=\fI'command'\fR | --exec=\fI'command'\fR] [--quiet | --silent]
+[--no-wtmp] [--no-encrypt]
+.PP
+.B tacc
+\fIusername\fR
+.SH DESCRIPTION
+.B tacc
+is simple TACACS+ client, designed mainly for two purposes:
+.TP
+.B command line mode
+sending arbitrary TACACS+ requests from command line
+.TP
+.B login mode
+sending authentication and authorization requests for username
+supplied in command line and password read from standard input;
+in this case \fBtacc\fR is working in "dumb" mode and compiled-in
+defaults are used
+.PP
+\fBtacc\fR will run in either command line mode or login mode depending
+on parameters passed from command line. When there's only one non-option
+(i.e. not starting with "\-") parameter, \fBtacc\fR will run in login mode.
+Otherwise, it will parse other parameters described below. Any data not
+explicitly set from command line will be set to default, compiled in
+value.
+.SH "COMMAND LINE OPTIONS"
+Options specifying action to be performed. At least one is required.
+.TP
+.I "\-T, \-\-authenticate"
+perform authentication of username and password supplied with
+\-u and \-p options
+.TP
+.I "\-R, \-\-authorize"
+request authorization for particular service (in current version
+this is limited to service \fBppp\fR and protocol \fBip\fR)
+.TP
+.I "\-A, \-\-account"
+send accounting requests on session beginning and end
+.IP "Data\ options"
+.TP
+.I "\-h, \-\-help"
+display brief listing of options and exit
+.TP
+.I "\-V, \-\-version"
+display program name, program version and exit
+
+.PP
+Options for passing various data to the program. Only \fB\-u\fR is required.
+If any of other options is not specified, the compiled-in defaul is used.
+
+.TP
+.I "\-u, \-\-username"
+username for authentication, authorization and accounting
+.TP
+.I "\-p, \-\-password"
+password for authentication; if not specified from command line,
+prompt is displayed and password is read from standard input
+.TP
+.I "\-s, \-\-server"
+TACACS+ server address, either IP address or host name; this option
+can be given up to two times, specifying primary and secondary servers
+.TP
+.I "\-k, \-\-secret"
+shared secret used to encrypt and decrypt packets exchanged with
+TACACS+ server
+.TP
+.I "\-i, \-\-sourceip"
+Set the IPv4 address used as the source IP address when communicating with
+the TACACS+ server. IPv6 addresses are not supported, nor are hostnames.
+The address must work when passsed to the
+.BR bind ()
+system call, that is, it must be valid for interface being used.
+.TP
+.I "\-c, \-\-command, \-\-exec"
+command to execute after successful performing all specified actions;
+this usually starts PPP or SLIP driver
+
+.PP
+Options modifying program's behaviour.
+
+.TP
+.I "\-q, \-\-quiet, \-\-silent"
+supresses displaying informational and error messages to standard
+output, but still report errors to syslog(3)
+.TP
+.I "\-w, \-\-no\-wtmp"
+supresses writing accounting records to local wtmp(5) files
+.TP
+.I "\-n, \-\-no\-encrypt"
+disables encryption of packets sent to TACACS+ server
+
+.SH "LOGIN MODE"
+In this mode \fBtacc\fR will accept only one parameter, the \fIusername\fR,
+as passed from \fIgetty\fR style programs. It is intended for use as
+replacement for \fIlogin\fR program in \fIgetty\fR configuration file.
+In login mode \fBtacc\fR will use compiled in defaults for server address,
+secret key and command to run after successful authentication and
+authorization. It will also prompt for password and read it from standard
+input, like the \fIlogin\fR program.
+
+.SH LIMITATIONS
+In current version there is no other way to change login mode parameters
+than changing them in the source file and recompiling \fBtacc\fR. It also
+does not implement the full TACACS+ specification, only the subset of
+TACACS+ required to perform PAP authentication, IP authorization
+and start/stop accounting.
+.PP
+There are also some limitations in information sent in accounting packets,
+caused by the fact that \fBtacc\fR doesn't perform PPP connection itself,
+but only spawns \fIpppd\fR to handle the PPP protocol. Because of this
+\fBtacc\fR has no idea on how many data was exchanged with the peer.
+
+.SH "EXIT CODE"
+\fBtacc\fR returns the following codes at exit:
+.TP
+.B 0
+to indicate success
+.TP
+.B 1
+to indicate authentication failure or remote server error
+.TP
+.B 2
+to indicate local error
+
+.SH "SEE ALSO"
+.TP
+.I ftp://ftp-eng.cisco.com/tacplus/
+CISCO archive with latest versions of TACACS+ specification, API and
+server source code
+.TP
+.I TAC_PLUS Developer's Kit
+distributed with TACACS+ server source code
+.TP
+.I http://ceti.com.pl/~kravietz/progs/tacacs.html
+more information on \fBtacc\fR and TACACS+ support for \fIpppd\fR
+
+.SH AUTHOR
+Pawel Krawczyk <kravietz@ceti.com.pl>
diff --git a/tacc.c b/tacc.c
new file mode 100644
index 0000000..289b6b3
--- /dev/null
+++ b/tacc.c
@@ -0,0 +1,601 @@
+/* tacc.c TACACS+ PAP authentication client
+ *
+ * Copyright 1997-98 by Pawel Krawczyk <kravietz@ceti.com.pl>
+ * Portions copyright (c) 1989 Carnegie Mellon University.
+ * Copyright 2018 Cumulus Networks, Inc. All rights reserved.
+ *
+ * See http://www.ceti.com.pl/~kravietz/progs/tacacs.html
+ * for details.
+ *
+ */
+
+#include <stdio.h>
+#include <pwd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <errno.h>
+#include <utmp.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <time.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <openssl/rand.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "tacplus.h"
+#include "libtac.h"
+
+/* Prompt displayed when asking for password */
+#define PASSWORD_PROMPT "Password: "
+
+/* if defined, given command will be run after
+ * successful authentication and proper wtmp
+ * entries will be made
+ */
+#define DEFAULT_COMMAND "/usr/sbin/pppd -detach"
+
+/* message that will be displayed to user
+ * before starting COMMAND
+ */
+#define COMMAND_MESSAGE "Starting PPP\n"
+
+/* timeout for reading password from user (seconds) */
+#define GETPASS_TIMEOUT 60
+
+/* end of CONFIGURABLE PARAMETERS */
+
+/* prototypes */
+void sighandler(int sig);
+void showusage(char *argv0);
+unsigned long getservername(char *serv);
+void showusage(char *progname);
+void showversion(char *progname);
+void authenticate(struct addrinfo *tac_server, const char *tac_secret,
+ const char *user, char *pass, char *tty, char *remote_addr);
+void timeout_handler(int signum);
+
+#define EXIT_OK 0
+#define EXIT_FAIL 1 /* AAA failure (or server error) */
+#define EXIT_ERR 2 /* local error */
+
+#define USE_SYSTEM 1
+
+/* globals */
+int tac_encryption = 1;
+typedef unsigned char flag;
+flag quiet = 0;
+char *user = NULL; /* global, because of signal handler */
+char *iface = NULL; /* -I interface or VRF to use for connection */
+struct sockaddr src_sockaddr;
+struct addrinfo src_addr_info;
+struct addrinfo *src_addr;
+
+int ip_addr_str_to_addr_info (const char *, struct addrinfo *);
+
+/* command line options */
+static struct option long_options[] = {
+/* operation */
+ { "authenticate", no_argument, NULL, 'T' },
+ { "authorize", no_argument, NULL, 'R' },
+ { "account", no_argument, NULL, 'A' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+
+/* data */
+ { "username", required_argument, NULL, 'u' },
+ { "remote", required_argument, NULL, 'r' },
+ { "password", required_argument, NULL, 'p' },
+ { "server", required_argument, NULL, 's' },
+ { "secret", required_argument, NULL, 'k' },
+ { "command", required_argument, NULL, 'c' },
+ { "exec", required_argument, NULL, 'c' },
+ { "service", required_argument, NULL, 'S' },
+ { "protocol", required_argument, NULL, 'P' },
+ { "remote", required_argument, NULL, 'r' },
+ { "interface", required_argument, NULL, 'I' },
+ { "sourceip", required_argument, NULL, 'i' },
+
+/* modifiers */
+ { "quiet", no_argument, NULL, 'q' },
+ { "silent", no_argument, NULL, 'q' },
+ { "no-wtmp", no_argument, NULL, 'w' },
+ { "no-encrypt", no_argument, NULL, 'n' },
+ { 0, 0, 0, 0 } };
+
+/* command line letters */
+char *opt_string = "TRAVIhu:p:s:k:c:qr:wnS:P:";
+
+int main(int argc, char **argv) {
+ char *pass = NULL;
+ char *tty;
+ char *command = NULL;
+ char *remote_addr = NULL;
+ char *service = NULL;
+ char *protocol = NULL;
+ struct addrinfo *tac_server;
+ char *tac_server_name = NULL;
+ char *tac_secret = NULL;
+ char *tac_srcip = NULL;
+ int tac_fd;
+ short int task_id = 0;
+ char buf[40];
+ int ret;
+#ifndef USE_SYSTEM
+ pid_t pid;
+#endif
+ char *msg;
+ struct areply arep;
+
+ /* options */
+ flag log_wtmp = 1;
+ flag do_author = 0;
+ flag do_authen = 0;
+ flag do_account = 0;
+ flag login_mode = 0;
+
+ /* check argc */
+ if (argc < 2) {
+ showusage(argv[0]);
+ exit(EXIT_ERR);
+ }
+
+ /* check for login mode */
+ if (argc == 2 && isalpha(*argv[1])) {
+ user = argv[1];
+ do_author = do_authen = do_account = 1;
+ command = DEFAULT_COMMAND;
+ login_mode = 1;
+ } else {
+ int c;
+ int opt_index;
+
+ while ((c = getopt_long(argc, argv, opt_string, long_options,
+ &opt_index)) != EOF) {
+ switch (c) {
+ case 'T':
+ do_authen = 1;
+ break;
+ case 'R':
+ do_author = 1;
+ break;
+ case 'A':
+ do_account = 1;
+ break;
+ case 'V':
+ showversion(argv[0]);
+ case 'h':
+ showusage(argv[0]);
+ case 'i':
+ tac_srcip = optarg;
+ break;
+ case 'I':
+ iface = optarg;
+ break;
+ case 'u':
+ user = optarg;
+ break;
+ case 'r':
+ remote_addr = optarg;
+ break;
+ case 'p':
+ pass = optarg;
+ break;
+ case 's':
+ tac_server_name = optarg;
+ break;
+ case 'k':
+ tac_secret = optarg;
+ break;
+ case 'c':
+ command = optarg;
+ break;
+ case 'S':
+ service = optarg;
+ break;
+ case 'P':
+ protocol = optarg;
+ break;
+ case 'q':
+ quiet = 1;
+ break;
+ case 'w':
+ log_wtmp = 0;
+ break;
+ case 'n':
+ tac_encryption = 0;
+ break;
+ }
+ }
+ }
+
+ /* check available information and set to defaults if needed */
+ if (do_authen + do_author + do_account == 0) {
+ printf("error: one of -TRAVh options is required\n");
+ exit(EXIT_ERR);
+ }
+
+ if (user == NULL) {
+ printf("error: username is required.\n");
+ exit(EXIT_ERR);
+ }
+
+ if (remote_addr == NULL) {
+ printf("error: remote address is required.\n");
+ exit(EXIT_ERR);
+ }
+
+ if (service == NULL) {
+ printf("error: service is required.\n");
+ exit(EXIT_ERR);
+ }
+
+ if (protocol == NULL) {
+ printf("error: protocol is required.\n");
+ exit(EXIT_ERR);
+ }
+
+ if (tac_server_name == NULL) {
+ printf("error: server name is required.\n");
+ exit(EXIT_ERR);
+ }
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ ret = getaddrinfo(tac_server_name, "tacacs", &hints, &tac_server);
+ if (ret != 0) {
+ printf("error: resolving name %s: %s", tac_server_name,
+ gai_strerror(ret));
+ exit(EXIT_ERR);
+ }
+
+ if (tac_srcip) {
+ src_addr_info.ai_addr = &src_sockaddr;
+ if (ip_addr_str_to_addr_info (tac_srcip, &src_addr_info) == 0)
+ src_addr = &src_addr_info;
+ else {
+ printf("error: unable to convert sourceip %s to an IP addr\n",
+ tac_srcip);
+ exit(EXIT_ERR);
+ }
+ }
+
+ if (tac_secret == NULL) {
+ printf("error: server secret is required.\n");
+ exit(EXIT_ERR);
+ }
+
+ if (pass == NULL) {
+ signal(SIGALRM, timeout_handler);
+ alarm(GETPASS_TIMEOUT);
+ pass = getpass(PASSWORD_PROMPT);
+ alarm(0);
+ signal(SIGALRM, SIG_DFL);
+ if (!strlen(pass))
+ exit(EXIT_ERR);
+ }
+
+ tty = ttyname(0);
+ if (strncmp(tty, "/dev/", 5) == 0)
+ tty += 5;
+
+ /* open syslog before any TACACS+ calls */
+ openlog("tacc", LOG_CONS | LOG_PID, LOG_AUTHPRIV);
+
+ if (do_authen)
+ authenticate(tac_server, tac_secret, user, pass, tty, remote_addr);
+
+ if (do_author) {
+ /* authorize user */
+ struct tac_attrib *attr = NULL;
+ tac_add_attrib(&attr, "service", service);
+ tac_add_attrib(&attr, "protocol", protocol);
+
+ tac_fd = tac_connect_single(tac_server, tac_secret, src_addr,
+ iface);
+ if (tac_fd < 0) {
+ if (!quiet)
+ printf("Error connecting to TACACS+ server: %m\n");
+ exit(EXIT_ERR);
+ }
+
+ tac_author_send(tac_fd, user, tty, remote_addr, attr);
+
+ tac_author_read(tac_fd, &arep);
+ if (arep.status != AUTHOR_STATUS_PASS_ADD
+ && arep.status != AUTHOR_STATUS_PASS_REPL) {
+ if (!quiet) {
+ struct tac_attrib *attr;
+ printf("Authorization FAILED: %s\n", arep.msg);
+ for (attr = arep.attr; attr != NULL;
+ attr = attr->next)
+ printf("Attribute %*s\n",
+ attr->attr_len, attr->attr);
+ }
+ exit(EXIT_FAIL);
+ } else {
+ if (!quiet)
+ printf("Authorization OK: %s\n", arep.msg);
+ }
+
+ tac_free_attrib(&attr);
+ }
+
+ /* we no longer need the password in our address space */
+ bzero(pass, strlen(pass));
+ pass = NULL;
+
+ if (do_account) {
+ /* start accounting */
+ struct tac_attrib *attr = NULL;
+ sprintf(buf, "%lu", time(0));
+ tac_add_attrib(&attr, "start_time", buf);
+#if defined(HAVE_OPENSSL_RAND_H) && defined(HAVE_LIBCRYPTO)
+ RAND_pseudo_bytes((unsigned char *) &task_id, sizeof(task_id));
+#else
+ task_id = tac_magic();
+#endif
+ sprintf(buf, "%hu", task_id);
+ tac_add_attrib(&attr, "task_id", buf);
+ tac_add_attrib(&attr, "service", service);
+ tac_add_attrib(&attr, "protocol", protocol);
+
+ tac_fd = tac_connect_single(tac_server, tac_secret, src_addr,
+ iface);
+ if (tac_fd < 0) {
+ if (!quiet)
+ printf("Error connecting to TACACS+ server: %m\n");
+ exit(EXIT_ERR);
+ }
+
+ tac_acct_send(tac_fd, TAC_PLUS_ACCT_FLAG_START, user, tty, remote_addr, attr);
+
+ ret = tac_acct_read(tac_fd, &arep);
+ if(ret == 0) {
+ if (!quiet)
+ printf("Accounting: START failed: %s\n", arep.msg);
+ syslog(LOG_INFO, "TACACS+ accounting start failed: %s", arep.msg);
+ } else if (!login_mode && !quiet)
+ printf("Accounting: START OK\n");
+
+ close(tac_fd);
+
+ tac_free_attrib(&attr);
+
+ }
+
+ /* log in local utmp */
+#ifdef HAVE_LOGWTMP
+ if (log_wtmp)
+ logwtmp(tty, user, "dialup");
+#endif
+
+ if (command != NULL) {
+ int ret;
+
+ syslog(LOG_DEBUG, "starting %s for %s", command, user);
+
+ signal(SIGHUP, SIG_IGN);
+ signal(SIGTERM, SIG_IGN);
+ signal(SIGINT, SIG_IGN);
+ signal(SIGCHLD, SIG_IGN);
+
+#ifdef COMMAND_MESSAGE
+ printf(COMMAND_MESSAGE);
+#endif
+
+#if USE_SYSTEM
+ ret = system(command);
+ if (ret < 0)
+ syslog(LOG_WARNING, "command failed: %m");
+ else
+ syslog(LOG_NOTICE, "command exit code %u", ret);
+#else
+ pid=fork();
+
+ if(pid == 0) {
+ /* child */
+
+ execl(DEFAULT_COMMAND, DEFAULT_COMMAND, ARGS, NULL);
+ syslog(LOG_ERR, "execl() failed: %m");
+ _exit(EXIT_FAIL);
+ }
+
+ if(pid < 0) {
+ /* error */
+ syslog(LOG_ERR, "fork failed: %m");
+ exit(EXIT_FAIL);
+ }
+
+ if(pid > 0) {
+ /* parent */
+ int st, r;
+
+ r=wait(&st);
+ }
+#endif
+ }
+
+ if (do_account) {
+ /* stop accounting */
+ struct tac_attrib *attr = NULL;
+ sprintf(buf, "%lu", time(0));
+ tac_add_attrib(&attr, "stop_time", buf);
+ sprintf(buf, "%hu", task_id);
+ tac_add_attrib(&attr, "task_id", buf);
+
+ tac_fd = tac_connect_single(tac_server, tac_secret, src_addr,
+ iface);
+ if (tac_fd < 0) {
+ if (!quiet)
+ printf("Error connecting to TACACS+ server: %m\n");
+ exit(EXIT_ERR);
+ }
+
+ tac_acct_send(tac_fd, TAC_PLUS_ACCT_FLAG_STOP, user, tty, remote_addr, attr);
+ ret = tac_acct_read(tac_fd, &arep);
+ if (ret == 0) {
+ if (!quiet)
+ printf("Accounting: STOP failed: %s", arep.msg);
+ syslog(LOG_INFO, "TACACS+ accounting stop failed: %s\n", arep.msg);
+ } else if (!login_mode && !quiet)
+ printf("Accounting: STOP OK\n");
+
+ close(tac_fd);
+
+ tac_free_attrib(&attr);
+ }
+
+ /* logout from utmp */
+#ifdef HAVE_LOGWTMP
+ if (log_wtmp)
+ logwtmp(tty, "", "");
+#endif
+
+ exit(EXIT_OK);
+}
+
+void sighandler(int sig) {
+ TACDEBUG((LOG_DEBUG, "caught signal %d", sig));
+}
+
+void authenticate(struct addrinfo *tac_server, const char *tac_secret,
+ const char *user, char *pass, char *tty, char *remote_addr) {
+ int tac_fd;
+ char *msg;
+ int ret;
+ struct areply arep;
+
+ tac_fd = tac_connect_single(tac_server, tac_secret, src_addr, iface);
+ if (tac_fd < 0) {
+ if (!quiet)
+ printf("Error connecting to TACACS+ server: %m\n");
+ exit(EXIT_ERR);
+ }
+
+ /* start authentication */
+
+ if (tac_authen_send(tac_fd, user, pass, tty, remote_addr, TAC_PLUS_AUTHEN_LOGIN) < 0) {
+ if (!quiet)
+ printf("Error sending query to TACACS+ server\n");
+ exit(EXIT_ERR);
+ }
+
+ ret = tac_authen_read(tac_fd, &arep);
+
+ if (ret != TAC_PLUS_AUTHEN_STATUS_PASS) {
+ if (!quiet)
+ printf("Authentication FAILED: %s\n", arep.msg);
+ syslog(LOG_ERR, "authentication failed for %s: %s", user, arep.msg);
+ exit(EXIT_FAIL);
+ }
+
+ if (!quiet)
+ printf("Authentication OK\n");
+ syslog(LOG_INFO, "authentication OK for %s", user);
+
+ close(tac_fd);
+}
+
+void showusage(char *progname) {
+ char *a;
+
+ a = rindex(progname, '/');
+ progname = (a == NULL) ? progname : ++a;
+
+ printf("%s -- simple TACACS+ client and login, version %u.%u.%u\n",
+ progname, tac_ver_major, tac_ver_minor, tac_ver_patch);
+ printf("Copyright 1997-2016 by Pawel Krawczyk <pawel.krawczyk@hush.com>\n");
+ printf("Usage: %s option [option, ...]\n\n", progname);
+ printf(" Action:\n");
+ printf(" -T, --authenticate perform authentication with username and password\n");
+ printf(" -R, --authorize perform authorization for requested service\n");
+ printf(" -A, --account account session beginning and end\n");
+ printf(" -h, --help display this help and exit\n");
+ printf(" -V, --version display version number and exit\n\n");
+ printf(" Data:\n");
+ printf(" -u, --username remote user name\n");
+ printf(" -p, --password remote user password\n");
+ printf(" -s, --server server IP address or FQDN\n");
+ printf(" -r, --remote remote client's IP address\n");
+ printf(" -S, --service requested service (e.g. ppp)\n");
+ printf(" -P, --protocol requested protocl (e.g. ip)\n");
+ printf(" -k, --secret server encryption key\n");
+ printf(" -c, --command command to execute after successful AAA\n");
+ printf(" --exec alias for --command\n\n");
+ printf(" Modifiers:\n");
+ printf(" -q, --quiet don't display messages to screen (but still\n");
+ printf(" --silent report them via syslog(3))\n");
+ printf(" -w, --no-wtmp don't write records to wtmp(5)\n");
+ printf(" -n, --no-encrypt don't encrypt AAA packets sent to servers\n\n");
+ printf("Example usage:\n\n");
+ printf(" tacc -TRA -u test1 -p test1 -s localhost -r 1.1.1.1 -k test1 -S ppp -P ip\n");
+
+ exit(EXIT_ERR);
+}
+
+void showversion(char *progname) {
+ char *a;
+
+ a = rindex(progname, '/');
+ progname = (a == NULL) ? progname : ++a;
+
+ printf("%s %u.%u.%u\n", progname, tac_ver_major, tac_ver_minor,
+ tac_ver_patch);
+ exit(EXIT_OK);
+}
+
+unsigned long getservername(char *serv) {
+ struct in_addr addr;
+ struct hostent *h;
+
+ if (inet_aton(serv, &addr) == 0) {
+ if ((h = gethostbyname(serv)) == NULL) {
+ herror("gethostbyname");
+ } else {
+ bcopy(h->h_addr, (char *)&addr, sizeof(struct in_addr));
+ return(addr.s_addr);
+ }
+ } else
+ return(addr.s_addr);
+
+ return (-1);
+}
+
+void timeout_handler(int signum) {
+ syslog(LOG_ERR, "timeout reading password from user %s", user);
+
+}
+
+/* Convert ip address string to address info.
+ * It returns 0 on success, or -1 otherwise
+ * It supports ipv4 only.
+ */
+int ip_addr_str_to_addr_info (const char *srcaddr, struct addrinfo *p_addr_info)
+{
+ struct sockaddr_in *s_in;
+
+ s_in = (struct sockaddr_in *)p_addr_info->ai_addr;
+ s_in->sin_family = AF_INET;
+ s_in->sin_addr.s_addr = INADDR_ANY;
+
+ if (inet_pton(AF_INET, srcaddr, &(s_in->sin_addr)) == 1) {
+ p_addr_info->ai_family = AF_INET;
+ p_addr_info->ai_addrlen = sizeof (struct sockaddr_in);
+ return 0;
+ }
+ return -1;
+}
diff --git a/tacplus_servers b/tacplus_servers
new file mode 100644
index 0000000..0d9eb3b
--- /dev/null
+++ b/tacplus_servers
@@ -0,0 +1,46 @@
+# This is a common file used by audisp-tacplus, libpam_tacplus, and
+# libtacplus_map config files as shipped.
+#
+# Any tac_plus client config can go here that is common to all users of this
+# file, but typically it's just the TACACS+ server IP address(es) and shared
+# secret(s)
+#
+# This file should normally be mode 600, if you care about the security
+# of your secret key. When set to mode 600 NSS lookups for TACACS users
+# will only work for tacacs users that are logged in, via the local mapping.
+# For root, lookups will work for any tacacs users, logged in or not.
+
+# Set a per-connection timeout of 10 seconds, and enable the use of
+# poll() when trying to read from tacacs servers.
+# Otherwise standard TCP timeouts apply.
+# Not set or set to a negative value disables use of poll().
+# There are usually multiple connection attempts per login.
+timeout=10
+
+#secret=tacacskey
+#server=192.168.0.30
+
+# If set, login/logout accounting records are sent to all servers in
+# the list, otherwise only to the first responding server
+# Also used by audisp-tacplus per-command accounting, if it sources this file.
+# acct_all=1
+
+# If the management network is in a vrf, set this variable to the vrf name.
+# This would usually be "mgmt"
+# When this variable is set, the connection to the TACACS+ accounting servers
+# will be made through the named vrf.
+#vrf=mgmt
+
+# Sets the IPv4 address used as the source IP address when communicating with
+# the TACACS+ server. IPv6 addresses are not supported, nor are hostnames.
+# The address must work when passsed to the bind() system call, that is, it must
+# be valid for the interface being used.
+# source_ip=192.168.1.3
+
+# If user_homedir=1, then tacacs users will be set to have a home directory
+# based on their login name, rather than the mapped tacacsN home directory.
+# mkhomedir_helper is used to create the directory if it does not exist (similar
+# to use of pam_mkhomedir.so). This flag is ignored for users with restricted
+# shells, e.g., users mapped to a tacacs privilege level that has enforced
+# per-command authorization (see the tacplus-restrict man page).
+# user_homedir=1
diff --git a/tacplus_servers.5 b/tacplus_servers.5
new file mode 100644
index 0000000..4bc05ec
--- /dev/null
+++ b/tacplus_servers.5
@@ -0,0 +1,139 @@
+.TH tacplus_servers 5
+.\" Copyright 2017,2018 Cumulus Networks, Inc. All rights reserved.
+.SH NAME
+/etc/tacplus_servers \- TACACS+ client configuration file
+.SH SYNOPSIS
+.B /etc/tacplus_servers
+is a common configuration file for the tacplus client libraries and tools.
+.SH DESCRIPTION
+Providing a common configuration file for the tacplus client tools and libraries
+simplifies configuration. For most uses, this is the only configuration file
+that needs to be modified to enable TACACS+ client use.
+.PP
+By default, all components read this file, enabling a single point of
+configuration for the TACACS server(s), debug settings, etc.
+.PP
+Because this file contains the shared secret key(s), it should not have
+world read permissions (it should be mode 600 and owned by root) in order
+to be secure.
+This means that some components (such as NSS) may not be able to read this
+file when running as non-root users; these components have additional
+configuration files for that reason. Typically the
+.B secret
+keyword should not be used in those files, unless they are also not world-readable.
+.PP
+There are also additional configuration files that apply to the separate
+components.
+.PP
+Not all components use all variables; some configuration variables are
+ignored by one or more components.
+.PP
+.TP
+.I debug=Number
+Output debugging information via syslog(3).
+Debugging is heavy, including passwords. Do not leave debugging enabled on a production switch once you have completed troubleshooting. Currently most components only check to see if it is non-zero. Some components will print additional debug if set to
+.BR 2 .
+.TP
+.I "server=IP_ADDRESS | HOSTNAME"
+Adds a TACACS+ server to the servers list. Servers will be queried in turn
+until a match is found, or no servers remain in the list. Can be specified up
+to 7 times. When the IP_ADDR form is used, it can be optionally followed by a
+port number, preceded by a ":". The default port is 49. An IP address is
+preferred, rather than a hostname, since some components may start prior to
+networking. May occur in any order with the
+.I secret
+parameter (below).
+.TP
+.I secret=STRING
+Secret key used to encrypt/decrypt packets sent to/received from the server.
+Can be specified more than once, and can be in any order with respect to the
+server= parameter. When fewer
+.I secret
+parameters are specified than
+.I server
+parameters, the last secret given is used for each of the remaining servers.
+This parameter should only be put into files such as /etc/tacplus_servers that
+are not world readable.
+.TP
+.I source_ip=IPv4_ADDRESS
+Set the IPv4 address used as the source IP address when communicating with
+the TACACS+ server. IPv6 addresses are not supported, nor are hostnames.
+The address must work when passsed to the
+.BR bind ()
+system call, that is, it must be valid for interface being used.
+.TP
+.I timeout=SECONDS
+Sets the TACACS+ server(s) per-connection timeout. The default value is 10 seconds.
+During login, there are usually at least 4 connections made to the TACACS+
+server, so a server that is down could lead to multiple timeouts.
+The libnss functionality typically sets a smaller timeout in it's
+own configuration file
+.IR /etc/tacplus_nss.conf .
+.TP
+.I login=STRING
+TACACS+ authentication service (pap, chap, or login). The default value is pap.
+.TP
+.I acct_all=1
+Configuration option for audisp_tacplus and pam_tacplus sending accounting records to all supplied servers (1), or the first server to respond (0). The default value is 1.
+When sending accounting records, the record is sent to all servers in the list if
+acct_all=1, which is the default. Set to
+.B 0
+if the accounting records should only be sent to the first server that responds.
+This is also normally used by the
+.I audisp-tacplus
+per-command accounting daemon, because it's default configuration file sources this file.
+.TP
+.I user_homedir=1
+This is not enabled by default. When enabled, separate home directories for
+each TACACS+ user are created when the TACACS+ user first logs in. By default
+the home directory in the mapping accounts in
+.I /etc/passwd
+(/home/tacacs0 ... /home/tacacs15) are used.
+This is not honored for accounts with restricted shells (when per-command
+authorization is enabled). When set, if the home directory does not exist, it
+is created with the
+.I mkhomedir_helper
+program, in the same manner as pam_mkhomedir.
+.TP
+.I vrf=VRFNAME
+If the management network is in a VRF, set this variable to the VRF name. This
+would usually be "mgmt". When this variable is set, the connection to the
+TACACS+ accounting servers is made through the named VRF. The client processes
+must be restarted after this is changed. Because the NSS libraries become part
+of processes such as sshd, this typically requires a reboot.
+.TP
+.I service=shell
+TACACS+ accounting and authorization service. Examples include shell, pap,
+ppp, and slip.
+The default value is shell.
+.TP
+.I protocol=ssh
+TACACS+ protocol field. This option is use dependent.
+PAM uses the SSH protocol.
+.TP
+.I include=/file/name
+Open the listed file, and continue to read configuration from that file,
+if the open is successful. This avoids duplication of configuration information.
+A maximum of 8 configuration files may be used.
+.SH "SEE ALSO"
+.BR tacplus_nss.conf (5),
+.BR audisp-tacplus (8),
+.BR pam_mkhomedir (8),
+.BR tacplus-auth (8),
+.BR tacplus-restrict (8)
+.SH FILES
+.I /etc/audisp/plugins.d/audisp-tacplus.conf
+- audisp plugin configuration
+.br
+.I /etc/audisp/audisp-tac_plus.conf
+- tacplus client configuration file for accounting. Any of the variables in
+this manual page may be added to this file, if you only want them to affect
+the TACACS+ accounting.
+.br
+.I /etc/tacplus_nss.conf
+- tacplus NSS client library configuration file.
+If you want to debug just NSS lookups, or have configuration variables that can
+be used by programs that do not run with root privileges, add the configuration
+variables listed in this manual page to this file
+.SH AUTHOR
+Dave Olson <olson@cumulusnetworks.com>