diff options
-rw-r--r-- | Changelog | 70 | ||||
-rw-r--r-- | INSTALL | 114 | ||||
-rw-r--r-- | LICENSE | 280 | ||||
-rw-r--r-- | Makefile | 74 | ||||
-rw-r--r-- | README | 52 | ||||
-rw-r--r-- | TODO | 15 | ||||
-rw-r--r-- | USAGE | 87 | ||||
-rw-r--r-- | index.html | 37 | ||||
-rw-r--r-- | md5.c | 281 | ||||
-rw-r--r-- | md5.h | 36 | ||||
-rw-r--r-- | pam_radius_auth.c | 1638 | ||||
-rw-r--r-- | pam_radius_auth.conf | 32 | ||||
-rw-r--r-- | pam_radius_auth.h | 109 | ||||
-rw-r--r-- | pam_radius_auth.spec | 51 | ||||
-rw-r--r-- | radius.h | 234 |
15 files changed, 3110 insertions, 0 deletions
diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..189a9f2 --- /dev/null +++ b/Changelog @@ -0,0 +1,70 @@ +1.3.17 +------ + Allow any number of retries, instead of only up to 3. + + Add ruser option, to authenticate as PAM_RUSER instead of PAM_USER, + to allow applications such as 'su' to authenticate as the real user. + Patch from David Mitchell. + + Add 'localifdown' option. + +1.3.16 +------ + Memory handling fixes, which caused the module to not work on RH9.0 + + Added dummy pam_sm_acct_mgmt() function, which is needed by pppd 2.4 + + Increase the allowed length of user names + +1.3.15 +------ + Bug fix: don't try to free() static storage when using skip_passwd. + + Implement retry option. + +1.3.14 +------ + Solaris 8 changed their header files for PAM. + Bug fix to work on HURD: Don't use PATH_MAX. + +1.3.13 +------ + Fix a bug where *no* module options would prevent it from finding + the configuration file. Jon Nelson <jnelson@securepipe.com> + +1.3.12 +------ + Solaris helpfully passes argc==1 and argv==NULL to the function + pam_private_session(). So we've got to check for that ridiculous + condition. + Based on comments from "David Black" <dblack@angara.com> + + Check that the response packet ID matches the request ID. + Based on a patch from Leon Vernikov <vernikov@cisco.com> + + Use Calling-Station-Id (string), instead of Login-IP-Host (IP address) + for the name of the host the user is logging in from. + Comments from Mike Smith <powertec@beeb.net> + + Fix for a buffer overflow from Vesselin Atanasov <vesselin@bgnet.bg> + + miscellanous bug fixes. Don't add password to accounting requests; + log more errors; add NAS-Port and NAS-Port-Type attributes to ALL + packets. + + Some patches based on input from Grzegorz Paszka <Grzegorz.Paszka@pik-net.pl> + +1.3.11 +------ + + Bug fixes from Jon Nelson <jnelson@securepipe.com> + + Bug fixes from robert.hendrickx@smals-mvm.be + + More debugging messages. + +1.3.10 +------ + +If no password is given, then add a blank password to the outgoing request. +This change ensures that the outgoing packet is RFC compliant. @@ -0,0 +1,114 @@ +********************************************************************** + Redhat Linux 4.2 (PAM 0.54) +********************************************************************** + + make. + + Copy 'pam_radius_auth.so' to /lib/security/pam_radius_auth.so + + In /etc/pam.conf, add the line: + +login auth sufficient /lib/security/pam_radius_auth.so + + AFTER + +login auth required /lib/security/pam_securetty.so + + and BEFORE + +login auth required /lib/security/pam_unix_auth.so + + i.e. + +login auth required /lib/security/pam_securetty.so +login auth sufficient /lib/security/pam_radius_auth.so +login auth required /lib/security/pam_unix_auth.so + + +********************************************************************** + Redhat Linux > 5.0 +********************************************************************** + + make. + + Copy 'pam_radius_auth.so' to /lib/security/pam_radius_auth.so + + In the per-application configuration (/etc/pam.d/application) add: + +auth sufficient /lib/security/pam_radius_auth.so + + AFTER + +auth required /lib/security/pam_securetty.so + + and BEFORE + +auth required /lib/security/pam_unix_auth.so + + i.e. + +auth required /lib/security/pam_securetty.so +auth sufficient /lib/security/pam_radius_auth.so +auth required /lib/security/pam_unix_auth.so + + +********************************************************************** + Solaris 2.6 +********************************************************************** + + make. + + Copy 'pam_radius_auth.so' to /usr/lib/security/pam_radius_auth.so.1 + + in /etc/pam.conf, add the line: + +login auth sufficient /usr/lib/security/pam_radius_auth.so.1 + + BEFORE + +login auth required /usr/lib/security/pam_unix_auth.so.1 + + You will probably also have to add the lines: + +telnet auth sufficient /usr/lib/security/pam_radius_auth.so.1 +telnet auth required /usr/lib/security/pam_unix.so.1 + + in order to perform network logins. + +---------------------------------------------------------------------- + + Password change requests are pretty much the same. Add a line like: + +passwd password sufficient /lib/security/pam_radius_auth.so + + And you're set. + + Note that password change requests will NOT work for RADIUS users +using challenge-response authentication. + +---------------------------------------------------------------------- + + If you're familiar with PAM, configuring RADIUS authentication for +other applications should be straightforward. + + Note that you should be *very* careful when configuring users who +use RADIUS challenge-response. They should *not* have a Unix password +defined, or the challenge-response token card may become meaningless. + + Users who have have a RADIUS challenge-response configuration must +enter an initial password, unless 'skip_passwd' (see below) is +defined. The password they enter may not be blank or empty. + +---------------------------------------------------------------------- + + You will need a server configuration file. An example is given in +the file pam_radius_auth.conf. You will need to copy this file to +/etc/raddb/server. The file MUST be secure! i.e. + +chown root /etc/raddb +chmod go-rwx /etc/raddb +chmod go-rwx /etc/raddb/server + + See 'USAGE' for details of the configuration file. + +---------------------------------------------------------------------- @@ -0,0 +1,280 @@ + 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/Makefile b/Makefile new file mode 100644 index 0000000..c050a2b --- /dev/null +++ b/Makefile @@ -0,0 +1,74 @@ +###################################################################### +# +# A minimal 'Makefile', by Alan DeKok <aland@freeradius.org> +# +# $Id: Makefile,v 1.13 2007/03/26 04:22:11 fcusack Exp $ +# +############################################################################# + +VERSION=1.3.17 + +###################################################################### +# +# If we're really paranoid, use these flags +#CFLAGS = -Wall -Wshadow -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs -Waggregate-return +# +# If you're not using GCC, then you'll have to change the CFLAGS. +# +CFLAGS = -Wall -fPIC +# +# On Irix, use this with MIPSPRo C Compiler, and don't forget to export CC=cc +# gcc on Irix does not work yet for pam_radius +# Also, use gmake instead of make +# Then copy pam_radius_auth.so to /usr/freeware/lib32/security (PAM dir) +# CFLAGS = + + +###################################################################### +# +# The default rule to build everything. +# +all: pam_radius_auth.so + +###################################################################### +# +# Build the object file from the C source. +# +pam_radius_auth.o: pam_radius_auth.c pam_radius_auth.h + $(CC) $(CFLAGS) -c pam_radius_auth.c -o pam_radius_auth.o +# +# This is what should work on Irix: +#pam_radius_auth.so: pam_radius_auth.o md5.o +# ld -shared pam_radius_auth.o md5.o -L/usr/freeware/lib32 -lpam -lc -o pam_radius_auth.so + + +###################################################################### +# +# Build the shared library. +# +# The -Bshareable flag *should* work on *most* operating systems. +# +# On Solaris, you might try using '-G', instead. +# +# On systems with a newer GCC, you will need to do: +# +# gcc -shared pam_radius_auth.o md5.o -lpam -lc -o pam_radius_auth.so +# +pam_radius_auth.so: pam_radius_auth.o md5.o + ld -Bshareable pam_radius_auth.o md5.o -lpam -o pam_radius_auth.so + +###################################################################### +# +# Check a distribution out of the source tree, and make a tar file. +# +dist: + cvs export -D now -d pam_radius-${VERSION} pam_radius + tar -cf pam_radius-${VERSION}.tar pam_radius-${VERSION} + rm -rf pam_radius-${VERSION} + +###################################################################### +# +# Clean up everything +# +clean: + @rm -f *~ *.so *.o @@ -0,0 +1,52 @@ + pam_radius_auth.c + =================== + + This is the PAM to RADIUS authentication module. It allows any +Linux or Solaris machine to become a RADIUS client for authentication +and password change requests. You will need to supply your own RADIUS +server to perform the actual authentication. + + The latest version has a simple merger of the original pam_radius +session accounting code which will work *only* on Linux. + + See INSTALL for instructions on building and installing this module. +I have successfully used it for RADIUS authentication on RedHat 4.2, +RedHat 5.x, RedHat 6.x, and Solaris 2.6. + + A number of options are supported by this module. See USAGE for +more details. + + Care should be taken when configuring RADIUS authentication. Your +RADIUS server should have a minimal set of machines in it's 'clients' +file. The server should NOT be visible to the world at large, but +should be contained behind a firewall. If your RADIUS server is +visible from the Internet, a number of attacks become possible. + + Any additional questions can be directed to: + + Alan DeKok (aland@freeradius.org) + + For the latest version and updates, see the main web or ftp site: + +http://www.freeradius.org/ +ftp://ftp.freeradius.org/pub/radius/ + + + The pam_radius_auth module based on an old version of Cristian +Gafton's pam_radius.c, and on an Apache module I wrote a while back. +(mod_auth_radius.c, also on ftp://ftp.freeradius.org/pub/radius/). + + The source contains a full suite of RADIUS functions, instead of +using libpwdb. It makes sense, because we want it to compile +out of the box on Linux and Solaris 2.6. I also wasn't able to find +much documentation for RADIUS authentication support in libpwdb, so I +rolled my own. + + There are minimal restrictions on using the code, as set out in the +disclaimer and copyright notice in pam_radius_auth.c. + + Building it is straightforward: use GNU make, and type 'make'. If +you've got some other weird make, you'll have to edit the Makefile to +remove the GNU make directives 'ifeq', 'else', etc. + + Alan DeKok <aland@freeradius.org> @@ -0,0 +1,15 @@ +------------------------------------------------------------ + + Note that root won't be able to change anyone's passwords via this +method, as RADIUS doesn't support the notion of root. + +------------------------------------------------------------ + + Add in pam_set_data && pam_get_data to keep track of which RADIUS + server we were talking to, and what the session_time was. + + Oddly enough, the session_time information seems to be happy to be + a 'static', but the radius_server_t *live_server doesn't. It works + for login, is re-used for open_session, but is ignored for close_session. + +------------------------------------------------------------ @@ -0,0 +1,87 @@ + The module takes a number of configuration options. Password changing +is not implemented, as the RADIUS protocol does not support it. + + The pam configuration can be: +... +auth sufficient /lib/security/pam_radius_auth.so [options] +... +account sufficient /lib/security/pam_radius_auth.so + +--------------------------------------------------------------------------- + + The 'options' section is optional, and can contain one or more of +the following strings. Note that not all of these options are +relevant in for all uses of the module. + +debug - print out extensive debugging information via pam_log. + These messages generally end up being handled by + sylog(), and go to /var/log/messages. Depending on + your host operating system, the log messages may be + elsewhere. + You should generally use the debug option when first + trying to install the module, as it will help + enormously in tracking down problems. + +use_first_pass - Instead of prompting the user for a password, retrieve + the password from the previous authentication module. + If the password does not exist, return failure. + If the password exists, try it, returning success/failure + as appropriate. + +try_first_pass - Instead of prompting the user for a password, retrieve + the password from the previous authentication module. + If the password exists, try it, and return success if it + passes. + If there was no previous password, or the previous password + fails authentication, prompt the user with + "Enter RADIUS password: ", and ask for another password. + Try this password, and return success/failure as appropriate. + + This is the default for authentication. + +skip_passwd - Do not prompt for a password, even if there was none + retrieved from the previous layer. + Send the previous one (if it exists), or else send a NULL + password. + If this fails, exit. + If an Access-Challenge is returned, display the challenge + message, and ask the user for the response. + Return success/failure as appropriate. + + The password sent to the next authentication module will + NOT be the response to the challenge. If a password from + a previous authentication module exists, it is passed on. + Otherwise, no password is sent to the next module. + +conf=foo - set the configuration filename to 'foo'. + Default is /etc/raddb/server + +client_id=bar - send a NAS-Identifier RADIUS attribute with string + 'bar'. If the client_id is not specified, the PAM_SERVICE + type is used instead. ('login', 'su', 'passwd', etc.) + This feature may be disabled by using 'client_id='. + i.e. A blank client ID. + +retry = # - allow a number of retries before continuing to the next + authentication module + +use_authtok - force the use of a previously entered password. + This is needed for pluggable password strength checking + i.e. try cracklib to be sure it's secure, then go update + the RADIUS server. + +ruser - If PAM_USER is root, Use the value of PAM_RUSER instead + of PAM_USER to determine the username to authenticate via + RADIUS. This is to allow 'su' to act like 'sudo'. + +localifdown - This option tells pam_radius to return PAM_IGNORE instead + of PAM_AUTHINFO_UNAVAIL if RADIUS auth failed due to + network unavailability. PAM_IGNORE tells the pam stack + to continue down the stack regardless of the control flag. + +accounting_bug - When used, the accounting response vector is NOT + validated. This option will probably only be necessary + on REALLY OLD (i.e. Livingston 1.16) servers. + +--------------------------------------------------------------------------- + diff --git a/index.html b/index.html new file mode 100644 index 0000000..c6fe96d --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ +<HTML> +<HEAD> +<TITLE><code>pam_radius_auth</CODE>: The PAM RADIUS authentication module</TITLE> +<BODY BGCOLOR=#FFFFFF TEXT=#000000> + +<H1><code>pam_radius_auth</CODE>: The PAM RADIUS authentication module</H1> + +This is the PAM to RADIUS authentication module. It allows any +PAM-capable machine to become a RADIUS client for authentication and +accounting requests. You will need a RADIUS server to perform the +actual authentication. + +<P><HR> +<H2>Files included with the module</H2> + +<A HREF="README">README</A> <EM>Introduction and documentation</EM><BR> +<A HREF="INSTALL">INSTALL</A> <EM>Installation instructions</EM><BR> +<A HREF="USAGE">USAGE</A> <EM>Module configuration and usage documentation</EM><BR> +<A HREF="Changelog">Changelog</A> <EM>What's changed</EM><BR> +<A HREF="pam_radius_auth.conf">pam_radius_auth.conf</A> <EM>Sample +configuration file for telling the client the location of the RADIUS server.</EM><BR> +<A HREF="pam_radius_auth.c">pam_radius_auth.c</A> <EM>C source file</EM><BR> +<A HREF="pam_radius_auth.h">pam_radius_auth.h</A> <EM>C header file</EM><BR> +<A HREF=""></A> <EM></EM><BR> + + +<P><HR> +<H2>Updates</H2> + +For the latest version and updates, see the main web or ftp site: +<P> +<A HREF="http://www.freeradius.org/pam_radius_auth/">http://www.freeradius.org/pam_radius_auth/</A><BR> +<A HREF="ftp://ftp.freeradius.org/pub/radius/">ftp://ftp.freeradius.org/pub/radius/</A> +<P><HR> +<EM>$Id: index.html,v 1.5 2003/09/19 14:44:43 aland Exp $</EM> +</BODY> +</HTML> @@ -0,0 +1,281 @@ +/* $Id: md5.c,v 1.3 2007/03/26 04:21:07 fcusack Exp $ + * + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * $Log: md5.c,v $ + * Revision 1.3 2007/03/26 04:21:07 fcusack + * use uint32_t (C99) not u_int32_t + * + * Revision 1.2 2002/06/28 06:29:21 fcusack + * change HIGHFIRST #ifdef from 'sun' to __sparc, and add __mips + * + * Revision 1.1.1.1 1999/08/19 13:13:26 aland + * Start of the pam_radius module + * + * Revision 1.2 1998/04/03 20:19:21 aland + * now builds cleanly on Solaris 2.6 + * + * Revision 1.1 1998/04/03 19:36:59 aland + * oh yeah, do MD5 stuff, too + * + * Revision 1.1 1996/12/01 03:06:54 morgan + * Initial revision + * + * Revision 1.1 1996/09/05 06:43:31 morgan + * Initial revision + * + */ + +#include <string.h> +#include "md5.h" + +#if defined(__sparc) || defined(__mips) +#define HIGHFIRST +#endif + +#ifndef HIGHFIRST +#define byteReverse(buf, len) /* Nothing */ +#else +void byteReverse(unsigned char *buf, unsigned longs); + +#ifndef ASM_MD5 +/* + * Note: this code is harmless on little-endian machines. + */ +void byteReverse(unsigned char *buf, unsigned longs) +{ + uint32_t t; + do { + t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | + ((unsigned) buf[1] << 8 | buf[0]); + *(uint32_t *) buf = t; + buf += 4; + } while (--longs); +} +#endif +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301U; + ctx->buf[1] = 0xefcdab89U; + ctx->buf[2] = 0x98badcfeU; + ctx->buf[3] = 0x10325476U; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void MD5Update(struct MD5Context *ctx, unsigned const char *buf, unsigned len) +{ + uint32_t t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy(p, buf, len); + return; + } + memcpy(p, buf, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void MD5Final(unsigned char digest[16], struct MD5Context *ctx) +{ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset(p, 0, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset(ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset(p, 0, count - 8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((uint32_t *) ctx->in)[14] = ctx->bits[0]; + ((uint32_t *) ctx->in)[15] = ctx->bits[1]; + + MD5Transform(ctx->buf, (uint32_t *) ctx->in); + byteReverse((unsigned char *) ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void MD5Transform(uint32_t buf[4], uint32_t const in[16]) +{ + register uint32_t a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478U, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756U, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070dbU, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceeeU, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0fafU, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62aU, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613U, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501U, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8U, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7afU, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1U, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7beU, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122U, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193U, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438eU, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821U, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562U, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340U, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51U, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aaU, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105dU, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453U, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681U, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8U, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6U, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6U, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87U, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14edU, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905U, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8U, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9U, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8aU, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942U, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681U, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122U, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380cU, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44U, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9U, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60U, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70U, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6U, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127faU, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085U, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05U, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039U, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5U, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8U, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665U, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244U, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97U, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7U, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039U, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3U, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92U, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47dU, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1U, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4fU, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0U, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314U, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1U, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82U, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235U, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bbU, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391U, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif @@ -0,0 +1,36 @@ +#ifndef MD5_H +#define MD5_H + +/* + * Some operating systems MAY resolve the MD5* functions to + * secret functions in one of their libraries. These OS supplied + * MD5 functions almost always blow up, and cause problems. + * To get around the issue, we re-define the MD5 function names + * so that we're sure that our module uses our tested and working + * MD5 functions. + */ +#define MD5Init pra_MD5Init +#define MD5Update pra_MD5Update +#define MD5Final pra_MD5Final +#define MD5Transform pra_MD5Transform + +#include <inttypes.h> + +struct MD5Context { + uint32_t buf[4]; + uint32_t bits[2]; + unsigned char in[64]; +}; + +void MD5Init(struct MD5Context *); +void MD5Update(struct MD5Context *, unsigned const char *, unsigned); +void MD5Final(unsigned char digest[16], struct MD5Context *); +void MD5Transform(uint32_t buf[4], uint32_t const in[16]); + +/* + * This is needed to make RSAREF happy on some MS-DOS compilers. + */ + +typedef struct MD5Context MD5_CTX; + +#endif /* MD5_H */ diff --git a/pam_radius_auth.c b/pam_radius_auth.c new file mode 100644 index 0000000..abea995 --- /dev/null +++ b/pam_radius_auth.c @@ -0,0 +1,1638 @@ +/* + * $Id: pam_radius_auth.c,v 1.39 2007/03/26 05:35:31 fcusack Exp $ + * pam_radius_auth + * Authenticate a user via a RADIUS session + * + * 0.9.0 - Didn't compile quite right. + * 0.9.1 - Hands off passwords properly. Solaris still isn't completely happy + * 0.9.2 - Solaris now does challenge-response. Added configuration file + * handling, and skip_passwd field + * 1.0.0 - Added handling of port name/number, and continue on select + * 1.1.0 - more options, password change requests work now, too. + * 1.1.1 - Added client_id=foo (NAS-Identifier), defaulting to PAM_SERVICE + * 1.1.2 - multi-server capability. + * 1.2.0 - ugly merger of pam_radius.c to get full RADIUS capability + * 1.3.0 - added my own accounting code. Simple, clean, and neat. + * 1.3.1 - Supports accounting port (oops!), and do accounting authentication + * 1.3.2 - added support again for 'skip_passwd' control flag. + * 1.3.10 - ALWAYS add Password attribute, to make packets RFC compliant. + * 1.3.11 - Bug fixes by Jon Nelson <jnelson@securepipe.com> + * 1.3.12 - miscellanous bug fixes. Don't add password to accounting + * requests; log more errors; add NAS-Port and NAS-Port-Type + * attributes to ALL packets. Some patches based on input from + * Grzegorz Paszka <Grzegorz.Paszka@pik-net.pl> + * 1.3.13 - Always update the configuration file, even if we're given + * no options. Patch from Jon Nelson <jnelson@securepipe.com> + * 1.3.14 - Don't use PATH_MAX, so it builds on GNU Hurd. + * 1.3.15 - Implement retry option, miscellanous bug fixes. + * 1.3.16 - Miscellaneous fixes (see CVS for history) + * 1.3.17 - Security fixes + * + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * The original pam_radius.c code is copyright (c) Cristian Gafton, 1996, + * <gafton@redhat.com> + * + * Some challenge-response code is copyright (c) CRYPTOCard Inc, 1998. + * All rights reserved. + */ + +#define PAM_SM_AUTH +#define PAM_SM_PASSWORD +#define PAM_SM_SESSION + +#include <limits.h> +#include <errno.h> + +#ifdef sun +#include <security/pam_appl.h> +#endif +#include <security/pam_modules.h> + +#include "pam_radius_auth.h" + +#define DPRINT if (ctrl & PAM_DEBUG_ARG) _pam_log + +/* internal data */ +static CONST char *pam_module_name = "pam_radius_auth"; +static char conf_file[BUFFER_SIZE]; /* configuration file */ + +/* we need to save these from open_session to close_session, since + * when close_session will be called we won't be root anymore and + * won't be able to access again the radius server configuration file + * -- cristiang */ +static radius_server_t *live_server = NULL; +static time_t session_time; + +/* logging */ +static void _pam_log(int err, CONST char *format, ...) +{ + va_list args; + char buffer[BUFFER_SIZE]; + + va_start(args, format); + vsprintf(buffer, format, args); + /* don't do openlog or closelog, but put our name in to be friendly */ + syslog(err, "%s: %s", pam_module_name, buffer); + va_end(args); +} + +/* argument parsing */ +static int _pam_parse(int argc, CONST char **argv, radius_conf_t *conf) +{ + int ctrl=0; + + memset(conf, 0, sizeof(radius_conf_t)); /* ensure it's initialized */ + + strcpy(conf_file, CONF_FILE); + + /* + * If either is not there, then we can't parse anything. + */ + if ((argc == 0) || (argv == NULL)) { + return ctrl; + } + + /* step through arguments */ + for (ctrl=0; argc-- > 0; ++argv) { + + /* generic options */ + if (!strncmp(*argv,"conf=",5)) { + strcpy(conf_file,*argv+5); + + } else if (!strcmp(*argv, "use_first_pass")) { + ctrl |= PAM_USE_FIRST_PASS; + + } else if (!strcmp(*argv, "try_first_pass")) { + ctrl |= PAM_TRY_FIRST_PASS; + + } else if (!strcmp(*argv, "skip_passwd")) { + ctrl |= PAM_SKIP_PASSWD; + + } else if (!strncmp(*argv, "retry=", 6)) { + conf->retries = atoi(*argv+6); + + } else if (!strcmp(*argv, "localifdown")) { + conf->localifdown = 1; + + } else if (!strncmp(*argv, "client_id=", 10)) { + if (conf->client_id) { + _pam_log(LOG_WARNING, "ignoring duplicate '%s'", *argv); + } else { + conf->client_id = (char *) *argv+10; /* point to the client-id */ + } + } else if (!strcmp(*argv, "accounting_bug")) { + conf->accounting_bug = TRUE; + + } else if (!strcmp(*argv, "ruser")) { + ctrl |= PAM_RUSER_ARG; + + } else if (!strcmp(*argv, "debug")) { + ctrl |= PAM_DEBUG_ARG; + conf->debug = 1; + + } else { + _pam_log(LOG_WARNING, "unrecognized option '%s'", *argv); + } + } + + return ctrl; +} + +/* Callback function used to free the saved return value for pam_setcred. */ +void _int_free( pam_handle_t * pamh, void *x, int error_status ) +{ + free(x); +} + +/************************************************************************* + * SMALL HELPER FUNCTIONS + *************************************************************************/ + +/* + * Return an IP address in host long notation from + * one supplied in standard dot notation. + */ +static UINT4 ipstr2long(char *ip_str) { + char buf[6]; + char *ptr; + int i; + int count; + UINT4 ipaddr; + int cur_byte; + + ipaddr = (UINT4)0; + + for(i = 0;i < 4;i++) { + ptr = buf; + count = 0; + *ptr = '\0'; + + while(*ip_str != '.' && *ip_str != '\0' && count < 4) { + if(!isdigit(*ip_str)) { + return((UINT4)0); + } + *ptr++ = *ip_str++; + count++; + } + + if(count >= 4 || count == 0) { + return((UINT4)0); + } + + *ptr = '\0'; + cur_byte = atoi(buf); + if(cur_byte < 0 || cur_byte > 255) { + return ((UINT4)0); + } + + ip_str++; + ipaddr = ipaddr << 8 | (UINT4)cur_byte; + } + return(ipaddr); +} + +/* + * Check for valid IP address in standard dot notation. + */ +static int good_ipaddr(char *addr) { + int dot_count; + int digit_count; + + dot_count = 0; + digit_count = 0; + while(*addr != '\0' && *addr != ' ') { + if(*addr == '.') { + dot_count++; + digit_count = 0; + } else if(!isdigit(*addr)) { + dot_count = 5; + } else { + digit_count++; + if(digit_count > 3) { + dot_count = 5; + } + } + addr++; + } + if(dot_count != 3) { + return(-1); + } else { + return(0); + } +} + +/* + * Return an IP address in host long notation from a host + * name or address in dot notation. + */ +static UINT4 get_ipaddr(char *host) { + struct hostent *hp; + + if(good_ipaddr(host) == 0) { + return(ipstr2long(host)); + + } else if((hp = gethostbyname(host)) == (struct hostent *)NULL) { + return((UINT4)0); + } + + return(ntohl(*(UINT4 *)hp->h_addr)); +} + +/* + * take server->hostname, and convert it to server->ip and server->port + */ +static int +host2server(radius_server_t *server) +{ + char *p; + int ctrl = 1; /* for DPRINT */ + + if ((p = strchr(server->hostname, ':')) != NULL) { + *(p++) = '\0'; /* split the port off from the host name */ + } + + if ((server->ip.s_addr = get_ipaddr(server->hostname)) == ((UINT4)0)) { + DPRINT(LOG_DEBUG, "DEBUG: get_ipaddr(%s) returned 0.\n", server->hostname); + return PAM_AUTHINFO_UNAVAIL; + } + + /* + * If the server port hasn't already been defined, go get it. + */ + if (!server->port) { + if (p && isdigit(*p)) { /* the port looks like it's a number */ + unsigned int i = atoi(p) & 0xffff; + + if (!server->accounting) { + server->port = htons((u_short) i); + } else { + server->port = htons((u_short) (i + 1)); + } + } else { /* the port looks like it's a name */ + struct servent *svp; + + if (p) { /* maybe it's not "radius" */ + svp = getservbyname (p, "udp"); + /* quotes allow distinction from above, lest p be radius or radacct */ + DPRINT(LOG_DEBUG, "DEBUG: getservbyname('%s', udp) returned %d.\n", p, svp); + *(--p) = ':'; /* be sure to put the delimiter back */ + } else { + if (!server->accounting) { + svp = getservbyname ("radius", "udp"); + DPRINT(LOG_DEBUG, "DEBUG: getservbyname(radius, udp) returned %d.\n", svp); + } else { + svp = getservbyname ("radacct", "udp"); + DPRINT(LOG_DEBUG, "DEBUG: getservbyname(radacct, udp) returned %d.\n", svp); + } + } + + if (svp == (struct servent *) 0) { + /* debugging above... */ + return PAM_AUTHINFO_UNAVAIL; + } + + server->port = svp->s_port; + } + } + + return PAM_SUCCESS; +} + +/* + * Do XOR of two buffers. + */ +static unsigned char * +xor(unsigned char *p, unsigned char *q, int length) +{ + int i; + unsigned char *retval = p; + + for (i = 0; i < length; i++) { + *(p++) ^= *(q++); + } + return retval; +} + +/************************************************************************** + * MID-LEVEL RADIUS CODE + **************************************************************************/ + +/* + * get a pseudo-random vector. + */ +static void +get_random_vector(unsigned char *vector) +{ +#ifdef linux + int fd = open("/dev/urandom",O_RDONLY); /* Linux: get *real* random numbers */ + int total = 0; + if (fd >= 0) { + while (total < AUTH_VECTOR_LEN) { + int bytes = read(fd, vector + total, AUTH_VECTOR_LEN - total); + if (bytes <= 0) + break; /* oops! Error */ + total += bytes; + } + close(fd); + } + + if (total != AUTH_VECTOR_LEN) +#endif + { /* do this *always* on other platforms */ + MD5_CTX my_md5; + struct timeval tv; + struct timezone tz; + static unsigned int session = 0; /* make the number harder to guess */ + + /* Use the time of day with the best resolution the system can + give us -- often close to microsecond accuracy. */ + gettimeofday(&tv,&tz); + + if (session == 0) { + session = getppid(); /* (possibly) hard to guess information */ + } + + tv.tv_sec ^= getpid() * session++; + + /* Hash things to get maybe cryptographically strong pseudo-random numbers */ + MD5Init(&my_md5); + MD5Update(&my_md5, (unsigned char *) &tv, sizeof(tv)); + MD5Update(&my_md5, (unsigned char *) &tz, sizeof(tz)); + MD5Final(vector, &my_md5); /* set the final vector */ + } +} + +/* + * RFC 2139 says to do generate the accounting request vector this way. + * However, the Livingston 1.16 server doesn't check it. The Cistron + * server (http://home.cistron.nl/~miquels/radius/) does, and this code + * seems to work with it. It also works with Funk's Steel-Belted RADIUS. + */ +static void +get_accounting_vector(AUTH_HDR *request, radius_server_t *server) +{ + MD5_CTX my_md5; + int secretlen = strlen(server->secret); + int len = ntohs(request->length); + + memset(request->vector, 0, AUTH_VECTOR_LEN); + MD5Init(&my_md5); + memcpy(((char *)request) + len, server->secret, secretlen); + + MD5Update(&my_md5, (unsigned char *)request, len + secretlen); + MD5Final(request->vector, &my_md5); /* set the final vector */ +} + +/* + * Verify the response from the server + */ +static int +verify_packet(char *secret, AUTH_HDR *response, AUTH_HDR *request) +{ + MD5_CTX my_md5; + unsigned char calculated[AUTH_VECTOR_LEN]; + unsigned char reply[AUTH_VECTOR_LEN]; + + /* + * We could dispense with the memcpy, and do MD5's of the packet + * + vector piece by piece. This is easier understand, and maybe faster. + */ + memcpy(reply, response->vector, AUTH_VECTOR_LEN); /* save the reply */ + memcpy(response->vector, request->vector, AUTH_VECTOR_LEN); /* sent vector */ + + /* MD5(response packet header + vector + response packet data + secret) */ + MD5Init(&my_md5); + MD5Update(&my_md5, (unsigned char *) response, ntohs(response->length)); + + /* + * This next bit is necessary because of a bug in the original Livingston + * RADIUS server. The authentication vector is *supposed* to be MD5'd + * with the old password (as the secret) for password changes. + * However, the old password isn't used. The "authentication" vector + * for the server reply packet is simply the MD5 of the reply packet. + * Odd, the code is 99% there, but the old password is never copied + * to the secret! + */ + if (*secret) { + MD5Update(&my_md5, (unsigned char *) secret, strlen(secret)); + } + + MD5Final(calculated, &my_md5); /* set the final vector */ + + /* Did he use the same random vector + shared secret? */ + if (memcmp(calculated, reply, AUTH_VECTOR_LEN) != 0) { + return FALSE; + } + return TRUE; +} + +/* + * Find an attribute in a RADIUS packet. Note that the packet length + * is *always* kept in network byte order. + */ +static attribute_t * +find_attribute(AUTH_HDR *response, unsigned char type) +{ + attribute_t *attr = (attribute_t *) &response->data; + + int len = ntohs(response->length) - AUTH_HDR_LEN; + + while (attr->attribute != type) { + if ((len -= attr->length) <= 0) { + return NULL; /* not found */ + } + attr = (attribute_t *) ((char *) attr + attr->length); + } + + return attr; +} + +/* + * Add an attribute to a RADIUS packet. + */ +static void +add_attribute(AUTH_HDR *request, unsigned char type, CONST unsigned char *data, int length) +{ + attribute_t *p; + + p = (attribute_t *) ((unsigned char *)request + ntohs(request->length)); + p->attribute = type; + p->length = length + 2; /* the total size of the attribute */ + request->length = htons(ntohs(request->length) + p->length); + memcpy(p->data, data, length); +} + +/* + * Add an integer attribute to a RADIUS packet. + */ +static void +add_int_attribute(AUTH_HDR *request, unsigned char type, int data) +{ + int value = htonl(data); + + add_attribute(request, type, (unsigned char *) &value, sizeof(int)); +} + +/* + * Add a RADIUS password attribute to the packet. Some magic is done here. + * + * If it's an PW_OLD_PASSWORD attribute, it's encrypted using the encrypted + * PW_PASSWORD attribute as the initialization vector. + * + * If the password attribute already exists, it's over-written. This allows + * us to simply call add_password to update the password for different + * servers. + */ +static void +add_password(AUTH_HDR *request, unsigned char type, CONST char *password, char *secret) +{ + MD5_CTX md5_secret, my_md5; + unsigned char misc[AUTH_VECTOR_LEN]; + int i; + int length = strlen(password); + unsigned char hashed[256 + AUTH_PASS_LEN]; /* can't be longer than this */ + unsigned char *vector; + attribute_t *attr; + + if (length > MAXPASS) { /* shorten the password for now */ + length = MAXPASS; + } + + if (length == 0) { + length = AUTH_PASS_LEN; /* 0 maps to 16 */ + } if ((length & (AUTH_PASS_LEN - 1)) != 0) { + length += (AUTH_PASS_LEN - 1); /* round it up */ + length &= ~(AUTH_PASS_LEN - 1); /* chop it off */ + } /* 16*N maps to itself */ + + memset(hashed, 0, length); + memcpy(hashed, password, strlen(password)); + + attr = find_attribute(request, PW_PASSWORD); + + if (type == PW_PASSWORD) { + vector = request->vector; + } else { + vector = attr->data; /* attr CANNOT be NULL here. */ + } + + /* ************************************************************ */ + /* encrypt the password */ + /* password : e[0] = p[0] ^ MD5(secret + vector) */ + MD5Init(&md5_secret); + MD5Update(&md5_secret, (unsigned char *) secret, strlen(secret)); + my_md5 = md5_secret; /* so we won't re-do the hash later */ + MD5Update(&my_md5, vector, AUTH_VECTOR_LEN); + MD5Final(misc, &my_md5); /* set the final vector */ + xor(hashed, misc, AUTH_PASS_LEN); + + /* For each step through, e[i] = p[i] ^ MD5(secret + e[i-1]) */ + for (i = 1; i < (length >> 4); i++) { + my_md5 = md5_secret; /* grab old value of the hash */ + MD5Update(&my_md5, &hashed[(i-1) * AUTH_PASS_LEN], AUTH_PASS_LEN); + MD5Final(misc, &my_md5); /* set the final vector */ + xor(&hashed[i * AUTH_PASS_LEN], misc, AUTH_PASS_LEN); + } + + if (type == PW_OLD_PASSWORD) { + attr = find_attribute(request, PW_OLD_PASSWORD); + } + + if (!attr) { + add_attribute(request, type, hashed, length); + } else { + memcpy(attr->data, hashed, length); /* overwrite the packet */ + } +} + +static void +cleanup(radius_server_t *server) +{ + radius_server_t *next; + + while (server) { + next = server->next; + _pam_drop(server->hostname); + _pam_forget(server->secret); + _pam_drop(server); + server = next; + } +} + +/* + * allocate and open a local port for communication with the RADIUS + * server + */ +static int +initialize(radius_conf_t *conf, int accounting) +{ + struct sockaddr salocal; + u_short local_port; + char hostname[BUFFER_SIZE]; + char secret[BUFFER_SIZE]; + + char buffer[BUFFER_SIZE]; + char *p; + FILE *fserver; + radius_server_t *server = NULL; + struct sockaddr_in * s_in; + int timeout; + int line = 0; + + /* the first time around, read the configuration file */ + if ((fserver = fopen (conf_file, "r")) == (FILE*)NULL) { + _pam_log(LOG_ERR, "Could not open configuration file %s: %s\n", + conf_file, strerror(errno)); + return PAM_ABORT; + } + + while (!feof(fserver) && + (fgets (buffer, sizeof(buffer), fserver) != (char*) NULL) && + (!ferror(fserver))) { + line++; + p = buffer; + + /* + * Skip blank lines and whitespace + */ + while (*p && + ((*p == ' ') || (*p == '\t') || + (*p == '\r') || (*p == '\n'))) p++; + + /* + * Nothing, or just a comment. Ignore the line. + */ + if ((!*p) || (*p == '#')) { + continue; + } + + timeout = 3; + if (sscanf(p, "%s %s %d", hostname, secret, &timeout) < 2) { + _pam_log(LOG_ERR, "ERROR reading %s, line %d: Could not read hostname or secret\n", + conf_file, line); + continue; /* invalid line */ + } else { /* read it in and save the data */ + radius_server_t *tmp; + + tmp = malloc(sizeof(radius_server_t)); + if (server) { + server->next = tmp; + server = server->next; + } else { + conf->server = tmp; + server= tmp; /* first time */ + } + + /* sometime later do memory checks here */ + server->hostname = strdup(hostname); + server->secret = strdup(secret); + server->accounting = accounting; + server->port = 0; + + if ((timeout < 1) || (timeout > 60)) { + server->timeout = 3; + } else { + server->timeout = timeout; + } + server->next = NULL; + } + } + fclose(fserver); + + if (!server) { /* no server found, die a horrible death */ + _pam_log(LOG_ERR, "No RADIUS server found in configuration file %s\n", + conf_file); + return PAM_AUTHINFO_UNAVAIL; + } + + /* open a socket. Dies if it fails */ + conf->sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (conf->sockfd < 0) { + _pam_log(LOG_ERR, "Failed to open RADIUS socket: %s\n", strerror(errno)); + return PAM_AUTHINFO_UNAVAIL; + } + + /* set up the local end of the socket communications */ + s_in = (struct sockaddr_in *) &salocal; + memset ((char *) s_in, '\0', sizeof(struct sockaddr)); + s_in->sin_family = AF_INET; + s_in->sin_addr.s_addr = INADDR_ANY; + + /* + * Use our process ID as a local port for RADIUS. + */ + local_port = (getpid() & 0x7fff) + 1024; + do { + local_port++; + s_in->sin_port = htons(local_port); + } while ((bind(conf->sockfd, &salocal, sizeof (struct sockaddr_in)) < 0) && + (local_port < 64000)); + + if (local_port >= 64000) { + close(conf->sockfd); + _pam_log(LOG_ERR, "No open port we could bind to."); + return PAM_AUTHINFO_UNAVAIL; + } + + return PAM_SUCCESS; +} + +/* + * Helper function for building a radius packet. + * It initializes *some* of the header, and adds common attributes. + */ +static void +build_radius_packet(AUTH_HDR *request, CONST char *user, CONST char *password, radius_conf_t *conf) +{ + char hostname[256]; + UINT4 ipaddr; + + hostname[0] = '\0'; + gethostname(hostname, sizeof(hostname) - 1); + + request->length = htons(AUTH_HDR_LEN); + + if (password) { /* make a random authentication req vector */ + get_random_vector(request->vector); + } + + add_attribute(request, PW_USER_NAME, (unsigned char *) user, strlen(user)); + + /* + * Add a password, if given. + */ + if (password) { + add_password(request, PW_PASSWORD, password, conf->server->secret); + + /* + * Add a NULL password to non-accounting requests. + */ + } else if (request->code != PW_ACCOUNTING_REQUEST) { + add_password(request, PW_PASSWORD, "", conf->server->secret); + } + + /* the packet is from localhost if on localhost, to make configs easier */ + if ((conf->server->ip.s_addr == ntohl(0x7f000001)) || (!hostname[0])) { + ipaddr = 0x7f000001; + } else { + struct hostent *hp; + + if ((hp = gethostbyname(hostname)) == (struct hostent *) NULL) { + ipaddr = 0x00000000; /* no client IP address */ + } else { + ipaddr = ntohl(*(UINT4 *) hp->h_addr); /* use the first one available */ + } + } + + /* If we can't find an IP address, then don't add one */ + if (ipaddr) { + add_int_attribute(request, PW_NAS_IP_ADDRESS, ipaddr); + } + + /* There's always a NAS identifier */ + if (conf->client_id && *conf->client_id) { + add_attribute(request, PW_NAS_IDENTIFIER, (unsigned char *) conf->client_id, + strlen(conf->client_id)); + } + + /* + * Add in the port (pid) and port type (virtual). + * + * We might want to give the TTY name here, too. + */ + add_int_attribute(request, PW_NAS_PORT_ID, getpid()); + add_int_attribute(request, PW_NAS_PORT_TYPE, PW_NAS_PORT_TYPE_VIRTUAL); +} + +/* + * Talk RADIUS to a server. + * Send a packet and get the response + */ +static int +talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *response, + char *password, char *old_password, int tries) +{ + int salen, total_length; + fd_set set; + struct timeval tv; + time_t now, end; + int rcode; + struct sockaddr saremote; + struct sockaddr_in *s_in = (struct sockaddr_in *) &saremote; + radius_server_t *server = conf->server; + int ok; + int server_tries; + int retval; + + /* ************************************************************ */ + /* Now that we're done building the request, we can send it */ + + /* + Hmm... on password change requests, all of the found server information + could be saved with a pam_set_data(), which means even the radius_conf_t + information will have to be malloc'd at some point + + On the other hand, we could just try all of the servers again in + sequence, on the off chance that one may have ended up fixing itself. + + */ + + /* loop over all available servers */ + while (server != NULL) { + + /* only look up IP information as necessary */ + if ((retval = host2server(server)) != PAM_SUCCESS) { + _pam_log(LOG_ERR, + "Failed looking up IP address for RADIUS server %s (errcode=%d)", + server->hostname, retval); + ok = FALSE; + goto next; /* skip to the next server */ + } + + /* set up per-server IP && port configuration */ + memset ((char *) s_in, '\0', sizeof(struct sockaddr)); + s_in->sin_family = AF_INET; + s_in->sin_addr.s_addr = htonl(server->ip.s_addr); + s_in->sin_port = server->port; + total_length = ntohs(request->length); + + if (!password) { /* make an RFC 2139 p6 request authenticator */ + get_accounting_vector(request, server); + } + + server_tries = tries; +send: + /* send the packet */ + if (sendto(conf->sockfd, (char *) request, total_length, 0, + &saremote, sizeof(struct sockaddr_in)) < 0) { + _pam_log(LOG_ERR, "Error sending RADIUS packet to server %s: %s", + server->hostname, strerror(errno)); + ok = FALSE; + goto next; /* skip to the next server */ + } + + /* ************************************************************ */ + /* Wait for the response, and verify it. */ + salen = sizeof(struct sockaddr); + tv.tv_sec = server->timeout; /* wait for the specified time */ + tv.tv_usec = 0; + FD_ZERO(&set); /* clear out the set */ + FD_SET(conf->sockfd, &set); /* wait only for the RADIUS UDP socket */ + + time(&now); + end = now + tv.tv_sec; + + /* loop, waiting for the select to return data */ + ok = TRUE; + while (ok) { + + rcode = select(conf->sockfd + 1, &set, NULL, NULL, &tv); + + /* select timed out */ + if (rcode == 0) { + _pam_log(LOG_ERR, "RADIUS server %s failed to respond", + server->hostname); + if (--server_tries) + goto send; + ok = FALSE; + break; /* exit from the select loop */ + } else if (rcode < 0) { + + /* select had an error */ + if (errno == EINTR) { /* we were interrupted */ + time(&now); + + if (now > end) { + _pam_log(LOG_ERR, "RADIUS server %s failed to respond", + server->hostname); + if (--server_tries) + goto send; + ok = FALSE; + break; /* exit from the select loop */ + } + + tv.tv_sec = end - now; + if (tv.tv_sec == 0) { /* keep waiting */ + tv.tv_sec = 1; + } + + } else { /* not an interrupt, it was a real error */ + _pam_log(LOG_ERR, "Error waiting for response from RADIUS server %s: %s", + server->hostname, strerror(errno)); + ok = FALSE; + break; + } + + /* the select returned OK */ + } else if (FD_ISSET(conf->sockfd, &set)) { + + /* try to receive some data */ + if ((total_length = recvfrom(conf->sockfd, (char *) response, + BUFFER_SIZE, + 0, &saremote, &salen)) < 0) { + _pam_log(LOG_ERR, "error reading RADIUS packet from server %s: %s", + server->hostname, strerror(errno)); + ok = FALSE; + break; + + /* there's data, see if it's valid */ + } else { + char *p = server->secret; + + if ((ntohs(response->length) != total_length) || + (ntohs(response->length) > BUFFER_SIZE)) { + _pam_log(LOG_ERR, "RADIUS packet from server %s is corrupted", + server->hostname); + ok = FALSE; + break; + } + + /* Check if we have the data OK. We should also check request->id */ + + if (password) { + if (old_password) { +#ifdef LIVINGSTON_PASSWORD_VERIFY_BUG_FIXED + p = old_password; /* what it should be */ +#else + p = ""; /* what it really is */ +#endif + } + /* + * RFC 2139 p.6 says not do do this, but the Livingston 1.16 + * server disagrees. If the user says he wants the bug, give in. + */ + } else { /* authentication request */ + if (conf->accounting_bug) { + p = ""; + } + } + + if (!verify_packet(p, response, request)) { + _pam_log(LOG_ERR, "packet from RADIUS server %s fails verification: The shared secret is probably incorrect.", + server->hostname); + ok = FALSE; + break; + } + + /* + * Check that the response ID matches the request ID. + */ + if (response->id != request->id) { + _pam_log(LOG_WARNING, "Response packet ID %d does not match the request packet ID %d: verification of packet fails", response->id, request->id); + ok = FALSE; + break; + } + } + + /* + * Whew! The select is done. It hasn't timed out, or errored out. + * It's our descriptor. We've got some data. It's the right size. + * The packet is valid. + * NOW, we can skip out of the select loop, and process the packet + */ + break; + } + /* otherwise, we've got data on another descriptor, keep select'ing */ + } + + /* go to the next server if this one didn't respond */ + next: + if (!ok) { + radius_server_t *old; /* forget about this server */ + + old = server; + server = server->next; + conf->server = server; + + _pam_forget(old->secret); + free(old->hostname); + free(old); + + if (server) { /* if there's more servers to check */ + /* get a new authentication vector, and update the passwords */ + get_random_vector(request->vector); + request->id = request->vector[0]; + + /* update passwords, as appropriate */ + if (password) { + get_random_vector(request->vector); + if (old_password) { /* password change request */ + add_password(request, PW_PASSWORD, password, old_password); + add_password(request, PW_OLD_PASSWORD, old_password, old_password); + } else { /* authentication request */ + add_password(request, PW_PASSWORD, password, server->secret); + } + } + } + continue; + + } else { + /* we've found one that does respond, forget about the other servers */ + cleanup(server->next); + server->next = NULL; + live_server = server; /* we've got a live one! */ + break; + } + } + + if (!server) { + _pam_log(LOG_ERR, "All RADIUS servers failed to respond."); + if (conf->localifdown) + retval = PAM_IGNORE; + else + retval = PAM_AUTHINFO_UNAVAIL; + } else { + retval = PAM_SUCCESS; + } + + return retval; +} + +/************************************************************************** + * MIDLEVEL PAM CODE + **************************************************************************/ + +/* this is our front-end for module-application conversations */ + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { return retval; } + +static int rad_converse(pam_handle_t *pamh, int msg_style, char *message, char **password) +{ + CONST struct pam_conv *conv; + struct pam_message resp_msg; + CONST struct pam_message *msg[1]; + struct pam_response *resp = NULL; + int retval; + + resp_msg.msg_style = msg_style; + resp_msg.msg = message; + msg[0] = &resp_msg; + + /* grab the password */ + retval = pam_get_item(pamh, PAM_CONV, (CONST void **) &conv); + PAM_FAIL_CHECK; + + retval = conv->conv(1, msg, &resp,conv->appdata_ptr); + PAM_FAIL_CHECK; + + if (password) { /* assume msg.type needs a response */ + /* I'm not sure if this next bit is necessary on Linux */ +#ifdef sun + /* NULL response, fail authentication */ + if ((resp == NULL) || (resp->resp == NULL)) { + return PAM_SYSTEM_ERR; + } +#endif + + *password = resp->resp; + free(resp); + } + + return PAM_SUCCESS; +} + +/************************************************************************** + * GENERAL CODE + **************************************************************************/ + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { \ + int *pret = malloc( sizeof(int) ); \ + *pret = retval; \ + pam_set_data( pamh, "rad_setcred_return" \ + , (void *) pret, _int_free ); \ + return retval; } + +PAM_EXTERN int +pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc,CONST char **argv) +{ + CONST char *user; + CONST char **userinfo; + char *password = NULL; + CONST char *rhost; + char *resp2challenge = NULL; + int ctrl; + int retval = PAM_AUTH_ERR; + + char recv_buffer[4096]; + char send_buffer[4096]; + AUTH_HDR *request = (AUTH_HDR *) send_buffer; + AUTH_HDR *response = (AUTH_HDR *) recv_buffer; + radius_conf_t config; + + ctrl = _pam_parse(argc, argv, &config); + + /* grab the user name */ + retval = pam_get_user(pamh, &user, NULL); + PAM_FAIL_CHECK; + + /* check that they've entered something, and not too long, either */ + if ((user == NULL) || + (strlen(user) > MAXPWNAM)) { + int *pret = malloc( sizeof(int) ); + *pret = PAM_USER_UNKNOWN; + pam_set_data( pamh, "rad_setcred_return", (void *) pret, _int_free ); + + DPRINT(LOG_DEBUG, "User name was NULL, or too long"); + return PAM_USER_UNKNOWN; + } + DPRINT(LOG_DEBUG, "Got user name %s", user); + + if (ctrl & PAM_RUSER_ARG) { + retval = pam_get_item(pamh, PAM_RUSER, (CONST void **) &userinfo); + PAM_FAIL_CHECK; + DPRINT(LOG_DEBUG, "Got PAM_RUSER name %s", userinfo); + + if (!strcmp("root", user)) { + user = userinfo; + DPRINT(LOG_DEBUG, "Username now %s from ruser", user); + } else { + DPRINT(LOG_DEBUG, "Skipping ruser for non-root auth"); + }; + }; + + /* + * Get the IP address of the authentication server + * Then, open a socket, and bind it to a port + */ + retval = initialize(&config, FALSE); + PAM_FAIL_CHECK; + + /* + * If there's no client id specified, use the service type, to help + * keep track of which service is doing the authentication. + */ + if (!config.client_id) { + retval = pam_get_item(pamh, PAM_SERVICE, (CONST void **) &config.client_id); + PAM_FAIL_CHECK; + } + + /* now we've got a socket open, so we've got to clean it up on error */ +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } + + /* build and initialize the RADIUS packet */ + request->code = PW_AUTHENTICATION_REQUEST; + get_random_vector(request->vector); + request->id = request->vector[0]; /* this should be evenly distributed */ + + /* grab the password (if any) from the previous authentication layer */ + retval = pam_get_item(pamh, PAM_AUTHTOK, (CONST void **) &password); + PAM_FAIL_CHECK; + + if(password) { + password = strdup(password); + DPRINT(LOG_DEBUG, "Got password %s", password); + } + + /* no previous password: maybe get one from the user */ + if (!password) { + if (ctrl & PAM_USE_FIRST_PASS) { + retval = PAM_AUTH_ERR; /* use one pass only, stopping if it fails */ + goto error; + } + + /* check to see if we send a NULL password the first time around */ + if (!(ctrl & PAM_SKIP_PASSWD)) { + retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, "Password: ", &password); + PAM_FAIL_CHECK; + + } + } /* end of password == NULL */ + + build_radius_packet(request, user, password, &config); + /* not all servers understand this service type, but some do */ + add_int_attribute(request, PW_USER_SERVICE_TYPE, PW_AUTHENTICATE_ONLY); + + /* + * Tell the server which host the user is coming from. + * + * Note that this is NOT the IP address of the machine running PAM! + * It's the IP address of the client. + */ + retval = pam_get_item(pamh, PAM_RHOST, (CONST void **) &rhost); + PAM_FAIL_CHECK; + if (rhost) { + add_attribute(request, PW_CALLING_STATION_ID, (unsigned char *) rhost, + strlen(rhost)); + } + + DPRINT(LOG_DEBUG, "Sending RADIUS request code %d", request->code); + + retval = talk_radius(&config, request, response, password, + NULL, config.retries + 1); + PAM_FAIL_CHECK; + + DPRINT(LOG_DEBUG, "Got RADIUS response code %d", response->code); + + /* + * If we get an authentication failure, and we sent a NULL password, + * ask the user for one and continue. + * + * If we get an access challenge, then do a response, for as many + * challenges as we receive. + */ + while (response->code == PW_ACCESS_CHALLENGE) { + attribute_t *a_state, *a_reply; + char challenge[BUFFER_SIZE]; + + /* Now we do a bit more work: challenge the user, and get a response */ + if (((a_state = find_attribute(response, PW_STATE)) == NULL) || + ((a_reply = find_attribute(response, PW_REPLY_MESSAGE)) == NULL)) { + /* Actually, State isn't required. */ + _pam_log(LOG_ERR, "RADIUS Access-Challenge received with State or Reply-Message missing"); + retval = PAM_AUTHINFO_UNAVAIL; + goto error; + } + + /* + * Security fixes. + */ + if ((a_state->length <= 2) || (a_reply->length <= 2)) { + _pam_log(LOG_ERR, "RADIUS Access-Challenge received with invalid State or Reply-Message"); + retval = PAM_AUTHINFO_UNAVAIL; + goto error; + } + + memcpy(challenge, a_reply->data, a_reply->length - 2); + challenge[a_reply->length - 2] = 0; + + /* It's full challenge-response, we should have echo on */ + retval = rad_converse(pamh, PAM_PROMPT_ECHO_ON, challenge, &resp2challenge); + + /* now that we've got a response, build a new radius packet */ + build_radius_packet(request, user, resp2challenge, &config); + /* request->code is already PW_AUTHENTICATION_REQUEST */ + request->id++; /* one up from the request */ + + /* copy the state over from the servers response */ + add_attribute(request, PW_STATE, a_state->data, a_state->length - 2); + + retval = talk_radius(&config, request, response, resp2challenge, NULL, 1); + PAM_FAIL_CHECK; + + DPRINT(LOG_DEBUG, "Got response to challenge code %d", response->code); + } + + /* Whew! Done the pasword checks, look for an authentication acknowledge */ + if (response->code == PW_AUTHENTICATION_ACK) { + retval = PAM_SUCCESS; + } else { + retval = PAM_AUTH_ERR; /* authentication failure */ + +error: + /* If there was a password pass it to the next layer */ + if (password && *password) { + pam_set_item(pamh, PAM_AUTHTOK, password); + } + } + + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_DEBUG, "authentication %s" + , retval==PAM_SUCCESS ? "succeeded":"failed" ); + } + + close(config.sockfd); + cleanup(config.server); + _pam_forget(password); + _pam_forget(resp2challenge); + { + int *pret = malloc( sizeof(int) ); + *pret = retval; + pam_set_data( pamh, "rad_setcred_return", (void *) pret, _int_free ); + } + return retval; +} + +/* + * Return a value matching the return value of pam_sm_authenticate, for + * greatest compatibility. + * (Always returning PAM_SUCCESS breaks other authentication modules; + * always returning PAM_IGNORE breaks PAM when we're the only module.) + */ +PAM_EXTERN int +pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,CONST char **argv) +{ + int retval, *pret; + + retval = PAM_SUCCESS; + pret = &retval; + pam_get_data( pamh, "rad_setcred_return", (CONST void **) &pret ); + return *pret; +} + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) { return PAM_SESSION_ERR; } + +static int +pam_private_session(pam_handle_t *pamh, int flags, + int argc, CONST char **argv, + int status) +{ + CONST char *user; + int ctrl; + int retval = PAM_AUTH_ERR; + + char recv_buffer[4096]; + char send_buffer[4096]; + AUTH_HDR *request = (AUTH_HDR *) send_buffer; + AUTH_HDR *response = (AUTH_HDR *) recv_buffer; + radius_conf_t config; + + ctrl = _pam_parse(argc, argv, &config); + + /* grab the user name */ + retval = pam_get_user(pamh, &user, NULL); + PAM_FAIL_CHECK; + + /* check that they've entered something, and not too long, either */ + if ((user == NULL) || + (strlen(user) > MAXPWNAM)) { + return PAM_USER_UNKNOWN; + } + + /* + * Get the IP address of the authentication server + * Then, open a socket, and bind it to a port + */ + retval = initialize(&config, TRUE); + PAM_FAIL_CHECK; + + /* + * If there's no client id specified, use the service type, to help + * keep track of which service is doing the authentication. + */ + if (!config.client_id) { + retval = pam_get_item(pamh, PAM_SERVICE, (CONST void **) &config.client_id); + PAM_FAIL_CHECK; + } + + /* now we've got a socket open, so we've got to clean it up on error */ +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } + + /* build and initialize the RADIUS packet */ + request->code = PW_ACCOUNTING_REQUEST; + get_random_vector(request->vector); + request->id = request->vector[0]; /* this should be evenly distributed */ + + build_radius_packet(request, user, NULL, &config); + + add_int_attribute(request, PW_ACCT_STATUS_TYPE, status); + + sprintf(recv_buffer, "%08d", (int) getpid()); + add_attribute(request, PW_ACCT_SESSION_ID, (unsigned char *) recv_buffer, + strlen(recv_buffer)); + + add_int_attribute(request, PW_ACCT_AUTHENTIC, PW_AUTH_RADIUS); + + if (status == PW_STATUS_START) { + session_time = time(NULL); + } else { + add_int_attribute(request, PW_ACCT_SESSION_TIME, time(NULL) - session_time); + } + + retval = talk_radius(&config, request, response, NULL, NULL, 1); + PAM_FAIL_CHECK; + + /* oops! They don't have the right password. Complain and die. */ + if (response->code != PW_ACCOUNTING_RESPONSE) { + retval = PAM_PERM_DENIED; + goto error; + } + + retval = PAM_SUCCESS; + +error: + + close(config.sockfd); + cleanup(config.server); + + return retval; +} + +PAM_EXTERN int +pam_sm_open_session(pam_handle_t *pamh, int flags, + int argc, CONST char **argv) +{ + return pam_private_session(pamh, flags, argc, argv, PW_STATUS_START); +} + +PAM_EXTERN int +pam_sm_close_session(pam_handle_t *pamh, int flags, + int argc, CONST char **argv) +{ + return pam_private_session(pamh, flags, argc, argv, PW_STATUS_STOP); +} + +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {return retval; } +#define MAX_PASSWD_TRIES 3 + +PAM_EXTERN int +pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, CONST char **argv) +{ + CONST char *user; + char *password = NULL; + char *new_password = NULL; + char *check_password = NULL; + int ctrl; + int retval = PAM_AUTHTOK_ERR; + int attempts; + + char recv_buffer[4096]; + char send_buffer[4096]; + AUTH_HDR *request = (AUTH_HDR *) send_buffer; + AUTH_HDR *response = (AUTH_HDR *) recv_buffer; + radius_conf_t config; + + ctrl = _pam_parse(argc, argv, &config); + + /* grab the user name */ + retval = pam_get_user(pamh, &user, NULL); + PAM_FAIL_CHECK; + + /* check that they've entered something, and not too long, either */ + if ((user == NULL) || + (strlen(user) > MAXPWNAM)) { + return PAM_USER_UNKNOWN; + } + + /* + * Get the IP address of the authentication server + * Then, open a socket, and bind it to a port + */ + retval = initialize(&config, FALSE); + PAM_FAIL_CHECK; + + /* + * If there's no client id specified, use the service type, to help + * keep track of which service is doing the authentication. + */ + if (!config.client_id) { + retval = pam_get_item(pamh, PAM_SERVICE, (CONST void **) &config.client_id); + PAM_FAIL_CHECK; + } + + /* now we've got a socket open, so we've got to clean it up on error */ +#undef PAM_FAIL_CHECK +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto error; } + + /* grab the old password (if any) from the previous password layer */ + retval = pam_get_item(pamh, PAM_OLDAUTHTOK, (CONST void **) &password); + PAM_FAIL_CHECK; + if(password) password = strdup(password); + + /* grab the new password (if any) from the previous password layer */ + retval = pam_get_item(pamh, PAM_AUTHTOK, (CONST void **) &new_password); + PAM_FAIL_CHECK; + if(new_password) new_password = strdup(new_password); + + /* preliminary password change checks. */ + if (flags & PAM_PRELIM_CHECK) { + if (!password) { /* no previous password: ask for one */ + retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, "Password: ", &password); + PAM_FAIL_CHECK; + } + + /* + * We now check the password to see if it's the right one. + * If it isn't, we let the user try again. + * Note that RADIUS doesn't have any concept of 'root'. The only way + * that root can change someone's password is to log into the RADIUS + * server, and and change it there. + */ + + /* build and initialize the access request RADIUS packet */ + request->code = PW_AUTHENTICATION_REQUEST; + get_random_vector(request->vector); + request->id = request->vector[0]; /* this should be evenly distributed */ + + build_radius_packet(request, user, password, &config); + add_int_attribute(request, PW_USER_SERVICE_TYPE, PW_AUTHENTICATE_ONLY); + + retval = talk_radius(&config, request, response, password, NULL, 1); + PAM_FAIL_CHECK; + + /* oops! They don't have the right password. Complain and die. */ + if (response->code != PW_AUTHENTICATION_ACK) { + _pam_forget(password); + retval = PAM_PERM_DENIED; + goto error; + } + + /* + * We're now sure it's the right user. + * Ask for their new password, if appropriate + */ + + if (!new_password) { /* not found yet: ask for it */ + int new_attempts; + attempts = 0; + + /* loop, trying to get matching new passwords */ + while (attempts++ < 3) { + + /* loop, trying to get a new password */ + new_attempts = 0; + while (new_attempts++ < 3) { + retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, + "New password: ", &new_password); + PAM_FAIL_CHECK; + + /* the old password may be short. Check it, first. */ + if (strcmp(password, new_password) == 0) { /* are they the same? */ + rad_converse(pamh, PAM_ERROR_MSG, + "You must choose a new password.", NULL); + _pam_forget(new_password); + continue; + } else if (strlen(new_password) < 6) { + rad_converse(pamh, PAM_ERROR_MSG, "it's WAY too short", NULL); + _pam_forget(new_password); + continue; + } + + /* insert crypt password checking here */ + + break; /* the new password is OK */ + } + + if (new_attempts >= 3) { /* too many new password attempts: die */ + retval = PAM_AUTHTOK_ERR; + goto error; + } + + /* make sure of the password by asking for verification */ + retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, + "New password (again): ", &check_password); + PAM_FAIL_CHECK; + + retval = strcmp(new_password, check_password); + _pam_forget(check_password); + + /* if they don't match, don't pass them to the next module */ + if (retval != 0) { + _pam_forget(new_password); + rad_converse(pamh, PAM_ERROR_MSG, + "You must enter the same password twice.", NULL); + retval = PAM_AUTHTOK_ERR; + goto error; /* ??? maybe this should be a 'continue' ??? */ + } + + break; /* everything's fine */ + } /* loop, trying to get matching new passwords */ + + if (attempts >= 3) { /* too many new password attempts: die */ + retval = PAM_AUTHTOK_ERR; + goto error; + } + } /* now we have a new password which passes all of our tests */ + + /* + * Solaris 2.6 calls pam_sm_chauthtok only ONCE, with PAM_PRELIM_CHECK + * set. + */ +#ifndef sun + /* If told to update the authentication token, do so. */ + } else if (flags & PAM_UPDATE_AUTHTOK) { +#endif + + if (!password || !new_password) { /* ensure we've got passwords */ + retval = PAM_AUTHTOK_ERR; + goto error; + } + + /* build and initialize the password change request RADIUS packet */ + request->code = PW_PASSWORD_REQUEST; + get_random_vector(request->vector); + request->id = request->vector[0]; /* this should be evenly distributed */ + + /* the secret here can not be know to the user, so it's the new password */ + _pam_forget(config.server->secret); + config.server->secret = strdup(password); /* it's free'd later */ + + build_radius_packet(request, user, new_password, &config); + add_password(request, PW_OLD_PASSWORD, password, password); + + retval = talk_radius(&config, request, response, new_password, password, 1); + PAM_FAIL_CHECK; + + /* Whew! Done password changing, check for password acknowledge */ + if (response->code != PW_PASSWORD_ACK) { + retval = PAM_AUTHTOK_ERR; + goto error; + } + } + + /* + * Send the passwords to the next stage if preliminary checks fail, + * or if the password change request fails. + */ + if ((flags & PAM_PRELIM_CHECK) || (retval != PAM_SUCCESS)) { + error: + + /* If there was a password pass it to the next layer */ + if (password && *password) { + pam_set_item(pamh, PAM_OLDAUTHTOK, password); + } + + if (new_password && *new_password) { + pam_set_item(pamh, PAM_AUTHTOK, new_password); + } + } + + if (ctrl & PAM_DEBUG_ARG) { + _pam_log(LOG_DEBUG, "password change %s" + , retval==PAM_SUCCESS ? "succeeded":"failed" ); + } + + close(config.sockfd); + cleanup(config.server); + + _pam_forget(password); + _pam_forget(new_password); + return retval; +} + +/* + * Do nothing for account management. This is apparently needed by + * some programs. + */ +PAM_EXTERN int +pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc,CONST char **argv) +{ + int retval; + retval = PAM_SUCCESS; + return retval; +} + +#ifdef PAM_STATIC + +/* static module data */ + +struct pam_module _pam_radius_modstruct = { + "pam_radius_auth", + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok, +}; +#endif + diff --git a/pam_radius_auth.conf b/pam_radius_auth.conf new file mode 100644 index 0000000..576f1f3 --- /dev/null +++ b/pam_radius_auth.conf @@ -0,0 +1,32 @@ +# pam_radius_auth configuration file. Copy to: /etc/raddb/server +# +# For proper security, this file SHOULD have permissions 0600, +# that is readable by root, and NO ONE else. If anyone other than +# root can read this file, then they can spoof responses from the server! +# +# There are 3 fields per line in this file. There may be multiple +# lines. Blank lines or lines beginning with '#' are treated as +# comments, and are ignored. The fields are: +# +# server[:port] secret [timeout] +# +# the port name or number is optional. The default port name is +# "radius", and is looked up from /etc/services The timeout field is +# optional. The default timeout is 3 seconds. +# +# If multiple RADIUS server lines exist, they are tried in order. The +# first server to return success or failure causes the module to return +# success or failure. Only if a server fails to response is it skipped, +# and the next server in turn is used. +# +# The timeout field controls how many seconds the module waits before +# deciding that the server has failed to respond. +# +# server[:port] shared_secret timeout (s) +127.0.0.1 secret 1 +other-server other-secret 3 + +# +# having localhost in your radius configuration is a Good Thing. +# +# See the INSTALL file for pam.conf hints. diff --git a/pam_radius_auth.h b/pam_radius_auth.h new file mode 100644 index 0000000..7a3e897 --- /dev/null +++ b/pam_radius_auth.h @@ -0,0 +1,109 @@ +#ifndef PAM_RADIUS_H +#define PAM_RADIUS_H + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <syslog.h> +#include <stdarg.h> +#include <utmp.h> +#include <time.h> +#include <netinet/in.h> +#include <netdb.h> +#include <fcntl.h> + +#include "radius.h" +#include "md5.h" + + +/************************************************************************* + * Additional RADIUS definitions + *************************************************************************/ + +/* Per-attribute structure */ +typedef struct attribute_t { + unsigned char attribute; + unsigned char length; + unsigned char data[1]; +} attribute_t; + +typedef struct radius_server_t { + struct radius_server_t *next; + struct in_addr ip; + u_short port; + char *hostname; + char *secret; + int timeout; + int accounting; +} radius_server_t; + +typedef struct radius_conf_t { + radius_server_t *server; + int retries; + int localifdown; + char *client_id; + int accounting_bug; + int sockfd; + int debug; +} radius_conf_t; + + +/************************************************************************* + * Platform specific defines + *************************************************************************/ + +#ifdef sun +#define PAM_EXTERN extern +/* + * On older versions of Solaris, you may have to change this to: + * #define CONST + */ +#define CONST const +#else +#define CONST const +#endif + +/************************************************************************* + * Useful macros and defines + *************************************************************************/ + +#define _pam_forget(X) if (X) {memset(X, 0, strlen(X));free(X);X = NULL;} +#ifndef _pam_drop +#define _pam_drop(X) if (X) {free(X);X = NULL;} +#endif + +#define PAM_DEBUG_ARG 1 +#define PAM_SKIP_PASSWD 2 +#define PAM_USE_FIRST_PASS 4 +#define PAM_TRY_FIRST_PASS 8 +#define PAM_RUSER_ARG 16 + + +/* Module defines */ +#ifndef BUFFER_SIZE +#define BUFFER_SIZE 1024 +#endif /* BUFFER_SIZE */ +#define MAXPWNAM 253 /* maximum user name length. Server dependent, + * this is the default value + */ +#define MAXPASS 128 /* max password length. Again, depends on server + * compiled in. This is the default. + */ +#ifndef CONF_FILE /* the configuration file holding the server secret */ +#define CONF_FILE "/etc/raddb/server" +#endif /* CONF_FILE */ + +#ifndef FALSE +#define FALSE 0 +#undef TRUE +#define TRUE !FALSE +#endif + +#endif /* PAM_RADIUS_H */ diff --git a/pam_radius_auth.spec b/pam_radius_auth.spec new file mode 100644 index 0000000..4251c10 --- /dev/null +++ b/pam_radius_auth.spec @@ -0,0 +1,51 @@ +%define name pam_radius_auth +%define version 1.3.15 +%define release 0 + +Name: %{name} +Summary: PAM Module for RADIUS Authentication +Version: %{version} +Release: %{release} +Source: ftp://ftp.freeradius.org/pub/radius/pam_radius_auth-%{version}.tar +URL: http://www.freeradius.org/pam_radius_auth/ +Group: System Environment/Libraries +BuildRoot: %{_tmppath}/%{name}-buildroot +License: BSD-like or GNU GPL +Requires: pam + +%description +This is the PAM to RADIUS authentication module. It allows any PAM-capable +machine to become a RADIUS client for authentication and accounting +requests. You will need a RADIUS server to perform the actual +authentication. + +%prep +%setup -q -n pam_radius-%{version} + +%build +make + +%install +mkdir -p %{buildroot}/lib/security +cp -p pam_radius_auth.so %{buildroot}/lib/security +mkdir -p %{buildroot}/etc/raddb +[ -f %{buildroot}/etc/raddb/server ] || cp -p pam_radius_auth.conf %{buildroot}/etc/raddb/server +chown root %{buildroot}/etc/raddb/server +chgrp root %{buildroot}/etc/raddb/server +chmod 0600 %{buildroot}/etc/raddb/server + +%clean +[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT + +%postun +rmdir /etc/raddb || true + +%files +%defattr(-,root,root,0755) +%doc README INSTALL USAGE Changelog +%config /etc/raddb/server +/lib/security/pam_radius_auth.so + +%changelog +* Mon Jun 03 2002 Richie Laager <rlaager@wiktel.com> 1.3.15-0 +- Inital RPM Version diff --git a/radius.h b/radius.h new file mode 100644 index 0000000..d39e6b5 --- /dev/null +++ b/radius.h @@ -0,0 +1,234 @@ +/* + * + * RADIUS + * Remote Authentication Dial In User Service + * + * + * Livingston Enterprises, Inc. + * 6920 Koll Center Parkway + * Pleasanton, CA 94566 + * + * Copyright 1992 Livingston Enterprises, Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose and without fee is hereby granted, provided that this + * copyright and permission notice appear on all copies and supporting + * documentation, the name of Livingston Enterprises, Inc. not be used + * in advertising or publicity pertaining to distribution of the + * program without specific prior permission, and notice be given + * in supporting documentation that copying and distribution is by + * permission of Livingston Enterprises, Inc. + * + * Livingston Enterprises, Inc. makes no representations about + * the suitability of this software for any purpose. It is + * provided "as is" without express or implied warranty. + * + */ + +/* + * @(#)radius.h 1.9 11/14/94 + */ +#ifndef RADIUS_H +#define RADIUS_H + +#define AUTH_VECTOR_LEN 16 +#define AUTH_PASS_LEN 16 +#define AUTH_STRING_LEN 128 /* maximum of 254 */ + +#ifndef UINT4 +typedef unsigned long UINT4; +#endif + +typedef struct pw_auth_hdr { + u_char code; + u_char id; + u_short length; + u_char vector[AUTH_VECTOR_LEN]; + u_char data[2]; +} AUTH_HDR; + +#define AUTH_HDR_LEN 20 +#define CHAP_VALUE_LENGTH 16 + +#define PW_AUTH_UDP_PORT 1645 +#define PW_ACCT_UDP_PORT 1646 + +#define PW_TYPE_STRING 0 +#define PW_TYPE_INTEGER 1 +#define PW_TYPE_IPADDR 2 +#define PW_TYPE_DATE 3 + + +#define PW_AUTHENTICATION_REQUEST 1 +#define PW_AUTHENTICATION_ACK 2 +#define PW_AUTHENTICATION_REJECT 3 +#define PW_ACCOUNTING_REQUEST 4 +#define PW_ACCOUNTING_RESPONSE 5 +#define PW_ACCOUNTING_STATUS 6 +#define PW_PASSWORD_REQUEST 7 +#define PW_PASSWORD_ACK 8 +#define PW_PASSWORD_REJECT 9 +#define PW_ACCOUNTING_MESSAGE 10 +#define PW_ACCESS_CHALLENGE 11 + +#define PW_USER_NAME 1 +#define PW_PASSWORD 2 +#define PW_CHAP_PASSWORD 3 +#define PW_NAS_IP_ADDRESS 4 +#define PW_NAS_PORT_ID 5 +#define PW_USER_SERVICE_TYPE 6 +#define PW_FRAMED_PROTOCOL 7 +#define PW_FRAMED_ADDRESS 8 +#define PW_FRAMED_NETMASK 9 +#define PW_FRAMED_ROUTING 10 +#define PW_FRAMED_FILTER_ID 11 +#define PW_FRAMED_MTU 12 +#define PW_FRAMED_COMPRESSION 13 +#define PW_LOGIN_HOST 14 +#define PW_LOGIN_SERVICE 15 +#define PW_LOGIN_TCP_PORT 16 +#define PW_OLD_PASSWORD 17 +#define PW_REPLY_MESSAGE 18 +#define PW_CALLBACK_NUMBER 19 +#define PW_CALLBACK_ID 20 +#define PW_EXPIRATION 21 +#define PW_FRAMED_ROUTE 22 +#define PW_FRAMED_IPXNET 23 +#define PW_STATE 24 +#define PW_CLASS 25 /* string */ +#define PW_VENDOR_SPECIFIC 26 /* vendor */ +#define PW_SESSION_TIMEOUT 27 /* integer */ +#define PW_IDLE_TIMEOUT 28 /* integer */ +#define PW_TERMINATION_ACTION 29 /* integer */ +#define PW_CALLED_STATION_ID 30 /* string */ +#define PW_CALLING_STATION_ID 31 /* string */ +#define PW_NAS_IDENTIFIER 32 /* string */ +#define PW_PROXY_STATE 33 /* string */ +#define PW_LOGIN_LAT_SERVICE 34 /* string */ +#define PW_LOGIN_LAT_NODE 35 /* string */ +#define PW_LOGIN_LAT_GROUP 36 /* string */ +#define PW_FRAMED_APPLETALK_LINK 37 /* integer */ +#define PW_FRAMED_APPLETALK_NETWORK 38 /* integer */ +#define PW_FRAMED_APPLETALK_ZONE 39 /* string */ + +#define PW_ACCT_STATUS_TYPE 40 +#define PW_ACCT_DELAY_TIME 41 +#define PW_ACCT_INPUT_OCTETS 42 +#define PW_ACCT_OUTPUT_OCTETS 43 +#define PW_ACCT_SESSION_ID 44 +#define PW_ACCT_AUTHENTIC 45 +#define PW_ACCT_SESSION_TIME 46 + +#define PW_CHAP_CHALLENGE 60 /* string */ +#define PW_NAS_PORT_TYPE 61 /* integer */ +#define PW_PORT_LIMIT 62 /* integer */ +#define PW_LOGIN_LAT_PORT 63 /* string */ +#define PW_PROMPT 64 /* integer */ + +/* + * INTEGER TRANSLATIONS + */ + +/* USER TYPES */ + +#define PW_LOGIN_USER 1 +#define PW_FRAMED_USER 2 +#define PW_DIALBACK_LOGIN_USER 3 +#define PW_DIALBACK_FRAMED_USER 4 +#define PW_OUTBOUND_USER 5 +#define PW_SHELL_USER 6 + +/* FRAMED PROTOCOLS */ + +#define PW_PPP 1 +#define PW_SLIP 2 + +/* FRAMED ROUTING VALUES */ + +#define PW_NONE 0 +#define PW_BROADCAST 1 +#define PW_LISTEN 2 +#define PW_BROADCAST_LISTEN 3 + +/* NAS PORT TYPES */ +#define PW_NAS_PORT_TYPE_VIRTUAL 5 + +/* FRAMED COMPRESSION TYPES */ + +#define PW_VAN_JACOBSEN_TCP_IP 1 + +/* LOGIN SERVICES */ + +#define PW_TELNET 0 +#define PW_RLOGIN 1 +#define PW_TCP_CLEAR 2 +#define PW_PORTMASTER 3 +#define PW_AUTHENTICATE_ONLY 8 + +/* AUTHENTICATION LEVEL */ + +#define PW_AUTH_NONE 0 +#define PW_AUTH_RADIUS 1 +#define PW_AUTH_LOCAL 2 + +/* STATUS TYPES */ + +#define PW_STATUS_START 1 +#define PW_STATUS_STOP 2 +#define PW_STATUS_ALIVE 3 + +/* Default Database File Names */ + +#define RADIUS_DIR "/etc/raddb" +#define RADACCT_DIR "/usr/adm/radacct" + +#define RADIUS_DICTIONARY "dictionary" +#define RADIUS_CLIENTS "clients" +#define RADIUS_USERS "users" +#define RADIUS_HOLD "holdusers" +#define RADIUS_LOG "logfile" + +/* Server data structures */ + +typedef struct dict_attr { + char name[32]; + int value; + int type; + struct dict_attr *next; +} DICT_ATTR; + +typedef struct dict_value { + char attrname[32]; + char name[32]; + int value; + struct dict_value *next; +} DICT_VALUE; + +typedef struct value_pair { + char name[32]; + int attribute; + int type; + UINT4 lvalue; + char strvalue[AUTH_STRING_LEN]; + struct value_pair *next; +} VALUE_PAIR; + +typedef struct auth_req { + UINT4 ipaddr; + u_short udp_port; + u_char id; + u_char code; + u_char vector[16]; + u_char secret[16]; + VALUE_PAIR *request; + int child_pid; /* Process ID of child */ + UINT4 timestamp; + struct auth_req *next; /* Next active request */ +} AUTH_REQ; + +#define SECONDS_PER_DAY 86400 +#define MAX_REQUEST_TIME 30 +#define CLEANUP_DELAY 5 +#define MAX_REQUESTS 100 + +#endif /* RADIUS_H */ |