diff options
author | Christian Poessinger <christian@poessinger.com> | 2021-05-02 19:07:04 +0200 |
---|---|---|
committer | Christian Poessinger <christian@poessinger.com> | 2021-05-02 19:18:42 +0200 |
commit | ceb7d3cb30a23b4b148bc71491b3817e9e6e2778 (patch) | |
tree | 160ebe6294acb6a790790098b3861e58c0ca9ae4 | |
download | libpam-tacplus-ceb7d3cb30a23b4b148bc71491b3817e9e6e2778.tar.gz libpam-tacplus-ceb7d3cb30a23b4b148bc71491b3817e9e6e2778.zip |
Initial import of libpam-tacplus (1.4.3-cl3u4)
65 files changed, 7748 insertions, 0 deletions
@@ -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 @@ -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() @@ -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) + @@ -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> @@ -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 */ + @@ -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> @@ -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> |