summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Changelog70
-rw-r--r--INSTALL114
-rw-r--r--LICENSE280
-rw-r--r--Makefile74
-rw-r--r--README52
-rw-r--r--TODO15
-rw-r--r--USAGE87
-rw-r--r--index.html37
-rw-r--r--md5.c281
-rw-r--r--md5.h36
-rw-r--r--pam_radius_auth.c1638
-rw-r--r--pam_radius_auth.conf32
-rw-r--r--pam_radius_auth.h109
-rw-r--r--pam_radius_auth.spec51
-rw-r--r--radius.h234
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.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..3416170
--- /dev/null
+++ b/INSTALL
@@ -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.
+
+----------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c7aea18
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/README b/README
new file mode 100644
index 0000000..3e055d2
--- /dev/null
+++ b/README
@@ -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>
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..2dde785
--- /dev/null
+++ b/TODO
@@ -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.
+
+------------------------------------------------------------
diff --git a/USAGE b/USAGE
new file mode 100644
index 0000000..d850ddd
--- /dev/null
+++ b/USAGE
@@ -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>
diff --git a/md5.c b/md5.c
new file mode 100644
index 0000000..7744f2c
--- /dev/null
+++ b/md5.c
@@ -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
diff --git a/md5.h b/md5.h
new file mode 100644
index 0000000..1e2b198
--- /dev/null
+++ b/md5.h
@@ -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 */