diff -Nru libpam-radius-auth-1.4.0/Changelog libpam-radius-auth-2.0.0/Changelog --- libpam-radius-auth-1.4.0/Changelog 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/Changelog 2020-07-21 20:02:24.000000000 +0000 @@ -1,75 +1,19 @@ -1.4.0 ------ - - Many changes. See USAGE for details. - -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 - -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" - - Check that the response packet ID matches the request ID. - Based on a patch from Leon Vernikov - - 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 - - Fix for a buffer overflow from Vesselin Atanasov - - 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 - -1.3.11 ------- - - Bug fixes from Jon Nelson - - 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. +2.0.0 - 21 July 2020 + * Add IPv6 support + * environment variable attribute Framed-IP-Address when present + from Antonio Silva + * add Management-Privilege-Level attribute support + from Deepak Kumar Bhagat + * Honor radius prompt attribute during challenge response + From Koch + * Add support for Linux VRF (SO_BINDTODEVICE), use per-server sockets if src_ip or vrf are requested + * Use per-server socket if src_ip is set in config file + from Luca Boccassi + * many other Linux fixes from Luca Boccassi. + * Fix memory leak in add_nas_ip_address + from Matt Knutson + * Add Calling-Station-Id to accounting requests. + from Thomas Liske + * Thread safety fixes + from Samuel Varley + * Many other fixes from Justin Standring, mweissen13, and Benny Siegert. diff -Nru libpam-radius-auth-1.4.0/configure libpam-radius-auth-2.0.0/configure --- libpam-radius-auth-1.4.0/configure 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/configure 2020-07-21 20:02:24.000000000 +0000 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for pam_radius $Id$. +# Generated by GNU Autoconf 2.69 for pam_radius 1.5. # # Report bugs to . # @@ -580,8 +580,8 @@ # Identity of this package. PACKAGE_NAME='pam_radius' PACKAGE_TARNAME='pam_radius' -PACKAGE_VERSION='$Id$' -PACKAGE_STRING='pam_radius $Id$' +PACKAGE_VERSION='1.5' +PACKAGE_STRING='pam_radius 1.5' PACKAGE_BUGREPORT='http://bugs.freeradius.org' PACKAGE_URL='http://www.freeradius.org' @@ -1248,7 +1248,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures pam_radius $Id$ to adapt to many kinds of systems. +\`configure' configures pam_radius 1.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1314,7 +1314,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of pam_radius $Id$:";; + short | recursive ) echo "Configuration of pam_radius 1.5:";; esac cat <<\_ACEOF @@ -1403,7 +1403,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -pam_radius configure $Id$ +pam_radius configure 1.5 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1864,7 +1864,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by pam_radius $as_me $Id$, which was +It was created by pam_radius $as_me 1.5, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -4507,6 +4507,7 @@ sys/time.h \ sys/types.h \ syslog.h \ + poll.h \ time.h \ unistd.h \ utmp.h @@ -4540,7 +4541,13 @@ for ac_header in security/pam_modules.h pam/pam_modules.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` -ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" " + #ifdef HAVE_SECURITY_PAM_APPL_H + # include + #endif + + +" if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : cat >>confdefs.h <<_ACEOF #define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 @@ -5261,7 +5268,7 @@ HOSTINFO=$host -if test "x$werror" == "xyes"; then +if test "x$werror" = "xyes"; then CFLAGS="-Werror $CFLAGS" fi @@ -5777,7 +5784,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by pam_radius $as_me $Id$, which was +This file was extended by pam_radius $as_me 1.5, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -5831,7 +5838,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -pam_radius config.status $Id$ +pam_radius config.status 1.5 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff -Nru libpam-radius-auth-1.4.0/configure.ac libpam-radius-auth-2.0.0/configure.ac --- libpam-radius-auth-1.4.0/configure.ac 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/configure.ac 2020-07-21 20:02:24.000000000 +0000 @@ -23,7 +23,7 @@ AC_PREREQ([2.59]) export CFLAGS LIBS LDFLAGS CPPFLAGS -AC_INIT([pam_radius],[$]Id[$],[http://bugs.freeradius.org],,[http://www.freeradius.org]) +AC_INIT([pam_radius],1.5,[http://bugs.freeradius.org],,[http://www.freeradius.org]) AC_CONFIG_SRCDIR([src/pam_radius_auth.c]) AC_CONFIG_HEADER([src/config.h]) m4_include([m4/ax_cc.m4]) @@ -173,13 +173,20 @@ sys/time.h \ sys/types.h \ syslog.h \ + poll.h \ time.h \ unistd.h \ utmp.h ) AC_CHECK_HEADERS(security/pam_appl.h pam/pam_appl.h) -AC_CHECK_HEADERS(security/pam_modules.h pam/pam_modules.h) +AC_CHECK_HEADERS(security/pam_modules.h pam/pam_modules.h, [], [], + [ + #ifdef HAVE_SECURITY_PAM_APPL_H + # include + #endif + ] +) if test x"$ac_cv_header_security_pam_modules_h" != x"yes" -a x"$ac_cv_header_pam_modules_appl_h" != x"yes"; then AC_MSG_ERROR([pam_modules.h not found]) fi @@ -195,6 +202,17 @@ ] ) +dnl # +dnl # Linux requires sys/socket.h before linux/if.h +dnl # +AC_CHECK_HEADERS([linux/if.h],[],[], + [ + #if HAVE_SYS_SOCKET_H + # include + #endif + ] +) + dnl ############################################################# dnl # dnl # 4. Checks for typedefs @@ -299,6 +317,7 @@ inet_aton \ inet_pton \ inet_ntop \ + setsockopt \ strlcat \ strlcpy ) @@ -357,7 +376,7 @@ dnl # test programs. dnl # dnl ############################################################# -if test "x$werror" == "xyes"; then +if test "x$werror" = "xyes"; then CFLAGS="-Werror $CFLAGS" fi AC_SUBST(LIBS) diff -Nru libpam-radius-auth-1.4.0/debian/changelog libpam-radius-auth-2.0.0/debian/changelog --- libpam-radius-auth-1.4.0/debian/changelog 2021-01-29 16:01:06.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/changelog 2021-02-18 16:50:12.000000000 +0000 @@ -1,3 +1,33 @@ +libpam-radius-auth (2.0.0-1) unstable; urgency=medium + + * [2736aa5] d/libpam-radius-auth.install: Adjust install path + Debian is supporting the Multiarch idea, thus we need to install the + library into /lib/$DEB_HOST_MULTIARCH/security/ so it will get found. + + -- Carsten Schoenert Thu, 18 Feb 2021 17:50:12 +0100 + +libpam-radius-auth (2.0.0-1~exp1) experimental; urgency=medium + + * [60bdc37] New upstream version 2.0.0 + (Closes: #789144) + * [86a931c] remove current patch queue + * [ede4270] d/rules: add an override to the clean target + Upstream has some oblique logic in the top level Makefile if the user is + calling the target (dist)clean in case configure wasn't running or called + before. + * [a6a0d61] pam-configs: add config file for radius authentication + Starting with this version we provide a configuration file for the + pam-auth-update utility. Thanks Brett Delle Grazie for providing the + content for the config file! + (Closes: #614967) + * [858f509] Adding postinst and prerm hook + In order to get pam-auth-update called on package installations and also + in case of the removal of the package we need to add the hook files. + * [1418c7b] d/README.Debian: add info about PAM config file + * [ee10bc8] NEWS: create file to announce important info on install + + -- Carsten Schoenert Thu, 04 Feb 2021 18:53:30 +0100 + libpam-radius-auth (1.4.0-4) unstable; urgency=medium * [dd1321d] d/watch: adding a watch file diff -Nru libpam-radius-auth-1.4.0/debian/libpam-radius-auth.install libpam-radius-auth-2.0.0/debian/libpam-radius-auth.install --- libpam-radius-auth-1.4.0/debian/libpam-radius-auth.install 2021-01-29 15:37:02.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/libpam-radius-auth.install 2021-02-18 16:50:12.000000000 +0000 @@ -1,2 +1,3 @@ -pam_radius_auth.conf etc/ -pam_radius_auth.so lib/security/ +debian/pam-configs/radius usr/share/pam-configs +pam_radius_auth.conf etc +pam_radius_auth.so lib/${DEB_HOST_MULTIARCH}/security diff -Nru libpam-radius-auth-1.4.0/debian/libpam-radius-auth.NEWS libpam-radius-auth-2.0.0/debian/libpam-radius-auth.NEWS --- libpam-radius-auth-1.4.0/debian/libpam-radius-auth.NEWS 1970-01-01 00:00:00.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/libpam-radius-auth.NEWS 2021-02-18 16:50:12.000000000 +0000 @@ -0,0 +1,16 @@ +libpam-radius-auth (2.0.0-1~exp1) experimental; urgency=medium + + libpam-radius-auth ships a pam-config file starting on version 2.0.0 + + The package provides now a PAM profile under /usr/share/pam-configs/radius + which can be used with pam-auth-update. + The default standard behavior on package installation is to do nothing as the + config key 'Default' is set to 'no'. In case you want to use this config file + you need to modify at least this option and call 'pam-auth-update' afterwards. + + More information on the PAM configuration manager can be found in + PAM-AUTH-UPDATE(8) + or online on site: + https://manpages.debian.org/unstable/libpam-runtime/pam-auth-update.8.en.html + + -- Carsten Schoenert Thu, 4 Feb 2021 17:51:00 -0100 diff -Nru libpam-radius-auth-1.4.0/debian/libpam-radius-auth.postinst libpam-radius-auth-2.0.0/debian/libpam-radius-auth.postinst --- libpam-radius-auth-1.4.0/debian/libpam-radius-auth.postinst 1970-01-01 00:00:00.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/libpam-radius-auth.postinst 2021-02-18 16:50:12.000000000 +0000 @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +pam-auth-update --package radius + +#DEBHELPER# + +exit 0 diff -Nru libpam-radius-auth-1.4.0/debian/libpam-radius-auth.prerm libpam-radius-auth-2.0.0/debian/libpam-radius-auth.prerm --- libpam-radius-auth-1.4.0/debian/libpam-radius-auth.prerm 1970-01-01 00:00:00.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/libpam-radius-auth.prerm 2021-02-18 16:50:12.000000000 +0000 @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +if [ "$1" = "remove" ]; then + pam-auth-update --package --remove radius +fi + +#DEBHELPER# + +exit 0 diff -Nru libpam-radius-auth-1.4.0/debian/pam-configs/radius libpam-radius-auth-2.0.0/debian/pam-configs/radius --- libpam-radius-auth-1.4.0/debian/pam-configs/radius 1970-01-01 00:00:00.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/pam-configs/radius 2021-02-18 16:50:12.000000000 +0000 @@ -0,0 +1,11 @@ +Name: Radius Authentication +Default: no +Priority: 129 +Auth-Type: Primary +Auth-Initial: + [success=end default=ignore] pam_radius_auth.so +Auth: + [success=end default=ignore] pam_radius_auth.so use_first_pass +Account-Type: Additional +Account: + [success=ok new_authtok_reqd=done ignore=ignore user_unknown=ignore authinfo_unavail=ignore default=bad] pam_radius_auth.so diff -Nru libpam-radius-auth-1.4.0/debian/patches/CVE-2015-9542.fix libpam-radius-auth-2.0.0/debian/patches/CVE-2015-9542.fix --- libpam-radius-auth-1.4.0/debian/patches/CVE-2015-9542.fix 2021-01-29 15:37:02.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/patches/CVE-2015-9542.fix 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -Description: This patch fixes CVE-2015-9542. -Author: Justin Standring -Author: Utkarsh Gupta -Bug-Debian: https://bugs.debian.org/951396 -Origin: https://github.com/FreeRADIUS/pam_radius/commit/01173ec -Origin: https://github.com/FreeRADIUS/pam_radius/commit/6bae92d -Origin: https://github.com/FreeRADIUS/pam_radius/commit/ac2c1677 -Last-Update: 2020-02-21 - ---- a/src/pam_radius_auth.c -+++ b/src/pam_radius_auth.c -@@ -528,6 +528,9 @@ - length = MAXPASS; - } - -+ memcpy(hashed, password, length); -+ memset(hashed + length, 0, sizeof(hashed) - length); -+ - if (length == 0) { - length = AUTH_PASS_LEN; /* 0 maps to 16 */ - } if ((length & (AUTH_PASS_LEN - 1)) != 0) { -@@ -535,9 +538,6 @@ - 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) { diff -Nru libpam-radius-auth-1.4.0/debian/patches/series libpam-radius-auth-2.0.0/debian/patches/series --- libpam-radius-auth-1.4.0/debian/patches/series 2021-01-29 15:37:02.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -CVE-2015-9542.fix diff -Nru libpam-radius-auth-1.4.0/debian/README.Debian libpam-radius-auth-2.0.0/debian/README.Debian --- libpam-radius-auth-1.4.0/debian/README.Debian 2021-01-29 15:37:02.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/README.Debian 2021-02-18 16:50:12.000000000 +0000 @@ -3,3 +3,16 @@ Upstream has a default set to /etc/raddb/server that does not fit in Debian. Be aware that the documentation references has not been changed and they reflect upstream setups. + +Since version 2.0.0 this package ships a PAM configuration in +/usr/share/pam-configs/radius which is turn off as default as an consequence +of bug report #614967. +In case you want to make use of this shipped configuration file to interact +with pam-auth-update you need to change the key 'Default' to 'yes' and call +pam-auth-update manually afterwards. The package now also runs this +configuration manager script on package installation while executing the +postinst script and also on package removal. +See also pam-auth-update(8) for more information on the PAM configuration +management script. + + -- Carsten Schoenert Thur, 04 Jan 2021 18:30:00 +0100 diff -Nru libpam-radius-auth-1.4.0/debian/rules libpam-radius-auth-2.0.0/debian/rules --- libpam-radius-auth-1.4.0/debian/rules 2021-01-29 15:37:02.000000000 +0000 +++ libpam-radius-auth-2.0.0/debian/rules 2021-02-18 16:50:12.000000000 +0000 @@ -10,6 +10,10 @@ %: dh $@ +override_dh_auto_clean: + # work around some stupid logic in top level makefile + cd src && make clean + # Make dh_auto_build pass CPPFLAGS in CFLAGS and force into "plain" # makefile mode (because the configure here does not pass along e.g. # compiler variables like CC) diff -Nru libpam-radius-auth-1.4.0/Makefile libpam-radius-auth-2.0.0/Makefile --- libpam-radius-auth-1.4.0/Makefile 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/Makefile 2020-07-21 20:02:24.000000000 +0000 @@ -5,7 +5,7 @@ # $Id: Makefile,v 1.13 2007/03/26 04:22:11 fcusack Exp $ # ############################################################################# -VERSION=1.4.0 +VERSION=2.0.0 ###################################################################### # @@ -15,6 +15,7 @@ # 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 @@ -25,6 +26,8 @@ #LDFLAGS += -shared -Wl,--version-script=pamsymbols.ver LDFLAGS += -shared +$(if $(wildcard src/config.h),,$(error You must run './configure [options]' before doing 'make')) + ###################################################################### # # The default rule to build everything. @@ -35,11 +38,14 @@ # # Build the object file from the C source. # -pam_radius_auth.o: src/pam_radius_auth.c src/pam_radius_auth.h - $(CC) $(CFLAGS) -c $< -o $@ +export CFLAGS + +src/pam_radius_auth.o: src/pam_radius_auth.c src/pam_radius_auth.h + @$(MAKE) -C src $(notdir $@) + +src/md5.o: src/md5.c src/md5.h + @$(MAKE) -C src $(notdir $@) -md5.o: src/md5.c src/md5.h - $(CC) $(CFLAGS) -c $< -o $@ # # This is what should work on Irix: #pam_radius_auth.so: pam_radius_auth.o md5.o @@ -58,22 +64,27 @@ # # 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 - $(CC) $(LDFLAGS) pam_radius_auth.o md5.o -lpam -o pam_radius_auth.so +pam_radius_auth.so: src/pam_radius_auth.o src/md5.o + $(CC) $(LDFLAGS) $^ -lpam -o pam_radius_auth.so ###################################################################### # # Check a distribution out of the source tree, and make a tar file. # -.PHONY: dist -dist: +.PHONY: pam_radius-$(VERSION).tar.gz +pam_radius-$(VERSION).tar.gz: git archive --format=tar --prefix=pam_radius-$(VERSION)/ master | gzip > pam_radius-$(VERSION).tar.gz - gpg --default-key aland@freeradius.org -b pam_radius-$(VERSION).tar.gz +%.sig: % + gpg --default-key packages@freeradius.org -b $< + + +dist: pam_radius-$(VERSION).tar.gz pam_radius-$(VERSION).tar.gz.sig ###################################################################### # # Clean up everything # +.PHONY: clean clean: - @rm -f *~ *.so *.o + @rm -f *~ *.so *.o src/*.o src/*~ diff -Nru libpam-radius-auth-1.4.0/pam_radius_auth.conf libpam-radius-auth-2.0.0/pam_radius_auth.conf --- libpam-radius-auth-1.4.0/pam_radius_auth.conf 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/pam_radius_auth.conf 2020-07-21 20:02:24.000000000 +0000 @@ -4,15 +4,20 @@ # 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 +# There are 5 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] +# server[:port] secret [timeout [source_ip [vrf]]] # # 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. +# The source_ip field is optional and the default is none. +# The vrf field is optional and the default is none. +# +# For IPv6 literal addresses, the address has to be surrounded by +# square brackets as usual. E.g. [2001:0db8:85a3::4]. # # If multiple RADIUS server lines exist, they are tried in order. The # first server to return success or failure causes the module to return @@ -20,12 +25,27 @@ # 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 - +# deciding that the server has failed to respond. Timeouts MUST be +# between 3 and 60 seconds. If they are outside of this range, the +# timeouts are clamped to this range. +# +# The source_ip field can be used to make the library bind the socket +# that connects to that particular server to a particular IP address. +# Note: specifying a timeout field is mandatory due to config parsing, +# but if not needed it can be just set to the default of 3. +# +# The vrf field can be used on Linux to make the library bind the socket +# that connects to that particualar server to a particular VRF. +# See: https://www.kernel.org/doc/Documentation/networking/vrf.txt for +# more information. +# Note: specifying a source_ip field is mandatory due to config parsing, +# but if not needed it can be just set to 0. +# +# server[:port] shared_secret timeout (s) source_ip vrf +127.0.0.1 secret 3 +other-server other-secret 5 192.168.1.10 vrf-blue +[2001:0db8:85a3::4]:1812 other6-secret 3 [2001:0db8:85a3::3] vrf-red +other-other-server other-other-secret 5 0 vrf-blue # # having localhost in your radius configuration is a Good Thing. # diff -Nru libpam-radius-auth-1.4.0/pam_radius_auth.spec libpam-radius-auth-2.0.0/pam_radius_auth.spec --- libpam-radius-auth-1.4.0/pam_radius_auth.spec 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/pam_radius_auth.spec 2020-07-21 20:02:24.000000000 +0000 @@ -1,12 +1,12 @@ %define name pam_radius_auth -%define version 1.3.15 +%define version 1.4.1 %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 +Source: ftp://ftp.freeradius.org/pub/freeradius/pam_radius-%{version}.tar URL: http://www.freeradius.org/pam_radius_auth/ Group: System Environment/Libraries BuildRoot: %{_tmppath}/%{name}-buildroot @@ -23,6 +23,7 @@ %setup -q -n pam_radius-%{version} %build +./configure make %install @@ -30,9 +31,6 @@ 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 @@ -44,6 +42,7 @@ %defattr(-,root,root,0755) %doc README INSTALL USAGE Changelog %config /etc/raddb/server +%config %attr(0600,root,root) /etc/raddb/server /lib/security/pam_radius_auth.so %changelog diff -Nru libpam-radius-auth-1.4.0/README.rst libpam-radius-auth-2.0.0/README.rst --- libpam-radius-auth-1.4.0/README.rst 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/README.rst 2020-07-21 20:02:24.000000000 +0000 @@ -41,9 +41,9 @@ 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. +Building it is straightforward: use GNU make, and type ``./configure``, +followed by ``make``. If you've got some other weird make, you'll +have to edit the Makefile to remove the GNU make directives. Alan DeKok diff -Nru libpam-radius-auth-1.4.0/src/config.h.in libpam-radius-auth-2.0.0/src/config.h.in --- libpam-radius-auth-1.4.0/src/config.h.in 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/src/config.h.in 2020-07-21 20:02:24.000000000 +0000 @@ -62,6 +62,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_PAM_PAM_MODULES_H +/* Define to 1 if you have the header file. */ +#undef HAVE_POLL_H + /* Define to 1 if you have the header file. */ #undef HAVE_SECURITY_PAM_APPL_H diff -Nru libpam-radius-auth-1.4.0/src/Makefile libpam-radius-auth-2.0.0/src/Makefile --- libpam-radius-auth-1.4.0/src/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ libpam-radius-auth-2.0.0/src/Makefile 2020-07-21 20:02:24.000000000 +0000 @@ -0,0 +1,11 @@ +all: pam_radius_auth.o md5.o + +pam_radius_auth.o: pam_radius_auth.c pam_radius_auth.h config.h + $(CC) $(CFLAGS) -c $< -o $@ + +md5.o: md5.c md5.h + $(CC) $(CFLAGS) -c $< -o $@ + +.PHONY: clean +clean: + @rm -f *~ *.so *.o src/*.o src/*~ diff -Nru libpam-radius-auth-1.4.0/src/pam_radius_auth.c libpam-radius-auth-2.0.0/src/pam_radius_auth.c --- libpam-radius-auth-1.4.0/src/pam_radius_auth.c 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/src/pam_radius_auth.c 2020-07-21 20:02:24.000000000 +0000 @@ -3,33 +3,6 @@ * 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 - * 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 - * 1.3.13 - Always update the configuration file, even if we're given - * no options. Patch from Jon Nelson - * 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 - * 1.4.0 - bind to any open port, add add force_prompt, max_challenge, prompt options - * - * * 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 @@ -55,25 +28,12 @@ #define PAM_SM_PASSWORD #define PAM_SM_SESSION -#include -#include -#include - #include "pam_radius_auth.h" -#define DPRINT if (opt_debug & PAM_DEBUG_ARG) _pam_log +#define DPRINT if (debug) _pam_log /* internal data */ static CONST char *pam_module_name = "pam_radius_auth"; -static char conf_file[BUFFER_SIZE]; /* configuration file */ -static int opt_debug = FALSE; /* print debug info */ - -/* 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, ...) @@ -82,7 +42,7 @@ char buffer[BUFFER_SIZE]; va_start(args, format); - vsprintf(buffer, format, args); + vsnprintf(buffer, sizeof(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); @@ -95,7 +55,7 @@ memset(conf, 0, sizeof(radius_conf_t)); /* ensure it's initialized */ - strcpy(conf_file, CONF_FILE); + conf->conf_file = CONF_FILE; /* set the default prompt */ snprintf(conf->prompt, MAXPROMPT, "%s: ", DEFAULT_PROMPT); @@ -112,13 +72,7 @@ /* generic options */ if (!strncmp(*argv,"conf=",5)) { - /* protect against buffer overflow */ - if (strlen(*argv+5) >= sizeof(conf_file)) { - _pam_log(LOG_ERR, "conf= argument too long"); - conf_file[0] = 0; - return 0; - } - strcpy(conf_file,*argv+5); + conf->conf_file = *argv+5; } else if (!strcmp(*argv, "use_first_pass")) { ctrl |= PAM_USE_FIRST_PASS; @@ -149,8 +103,7 @@ } else if (!strcmp(*argv, "debug")) { ctrl |= PAM_DEBUG_ARG; - conf->debug = 1; - opt_debug = TRUE; + conf->debug = TRUE; } else if (!strncmp(*argv, "prompt=", 7)) { if (!strncmp(conf->prompt, (char*)*argv+7, MAXPROMPT)) { @@ -168,9 +121,15 @@ } else if (!strcmp(*argv, "force_prompt")) { conf->force_prompt= TRUE; + } else if (!strcmp(*argv, "prompt_attribute")) { + conf->prompt_attribute = TRUE; + } else if (!strncmp(*argv, "max_challenge=", 14)) { conf->max_challenge = atoi(*argv+14); + } else if (!strcmp(*argv, "privilege_level")) { + conf->privilege_level = TRUE; + } else { _pam_log(LOG_WARNING, "unrecognized option '%s'", *argv); } @@ -190,150 +149,92 @@ *************************************************************************/ /* - * Return an IP address in host long notation from - * one supplied in standard dot notation. + * A strerror_r() wrapper function to deal with its nuisances. */ -static uint32_t ipstr2long(char *ip_str) { - char buf[6]; - char *ptr; - int i; - int count; - uint32_t ipaddr; - int cur_byte; - - ipaddr = (uint32_t)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 (uint32_t)0; - } - *ptr++ = *ip_str++; - count++; - } - - if (count >= 4 || count == 0) { - return (uint32_t)0; - } - - *ptr = '\0'; - cur_byte = atoi(buf); - if (cur_byte < 0 || cur_byte > 255) { - return (uint32_t)0; - } - - ip_str++; - ipaddr = ipaddr << 8 | (uint32_t)cur_byte; +static void get_error_string(int errnum, char *buf, size_t buflen) { +#if !defined(__GLIBC__) || ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE) + /* XSI version of strerror_r(). */ + int retval = strerror_r(errnum, buf, buflen); + + /* POSIX does not state what will happen to the buffer if the function fails. + * Put it into a known state rather than leave it possibly uninitialized. */ + if (retval != 0 && buflen > (size_t)0) { + buf[0] = '\0'; } - return ipaddr; -} +#else + /* GNU version of strerror_r(). */ + char tmp_buf[BUFFER_SIZE]; + char *retval = strerror_r(errnum, tmp_buf, sizeof(tmp_buf)); -/* - * 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; - } + snprintf(buf, buflen, "%s", retval); +#endif } /* - * Return an IP address in host long notation from a host - * name or address in dot notation. + * Return an IP address as a struct sockaddr *. */ -static uint32_t get_ipaddr(char *host) { - struct hostent *hp; - - if (good_ipaddr(host) == 0) { - return ipstr2long(host); - } else if ((hp = gethostbyname(host)) == (struct hostent *)NULL) { - return (uint32_t)0; +static int get_ipaddr(char *host, struct sockaddr *addr, char *port) { + struct addrinfo hints; + struct addrinfo *results; + int r; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_ADDRCONFIG; + + r = getaddrinfo(host, port && port[0] ? port : NULL, &hints, &results); + if (r == 0) { + memcpy(addr, results->ai_addr, results->ai_addrlen); + freeaddrinfo(results); } - return ntohl(*(uint32_t *)hp->h_addr); + return r; } /* - * take server->hostname, and convert it to server->ip and server->port + * take server->hostname, and convert it to server->ip */ -static int host2server(radius_server_t *server) +static int host2server(int debug, radius_server_t *server) { - char *p; - - 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)) == ((uint32_t)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((uint16_t) i); - } else { - server->port = htons((uint16_t) (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 %p.\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 %p.\n", svp); - } else { - svp = getservbyname ("radacct", "udp"); - DPRINT(LOG_DEBUG, "DEBUG: getservbyname(radacct, udp) returned %p.\n", svp); - } + char hostbuffer[256]; + char tmp[256]; + char *hostname; + char *portstart; + char *p, *port; + int r, n; + + /* hostname might be [ipv6::address] */ + strncpy(hostbuffer, server->hostname, sizeof(hostbuffer) - 1); + hostbuffer[sizeof(hostbuffer) - 1] = 0; + hostname = hostbuffer; + portstart = hostbuffer; + if (hostname[0] == '[') { + if ((p = strchr(hostname, ']')) != NULL) { + hostname++; + *p++ = 0; + portstart = p; + } + } + if ((port = strchr(portstart, ':')) != NULL) { + *port++ = '\0'; + if (isdigit((unsigned char)*port) && server->accounting) { + if (sscanf(port, "%d", &n) == 1) { + snprintf(tmp, sizeof(tmp), "%d", n + 1); + port = tmp; } - - if (svp == (struct servent *) 0) { - /* debugging above... */ - return PAM_AUTHINFO_UNAVAIL; - } - - server->port = svp->s_port; } + } else { + if (server->accounting) + port = "radacct"; + else + port = "radius"; } - return PAM_SUCCESS; + server->ip = (struct sockaddr *)&server->ip_storage; + r = get_ipaddr(hostname, server->ip, port); + DPRINT(LOG_DEBUG, "DEBUG: get_ipaddr(%s) returned %d.\n", hostname, r); + return r; } /* @@ -504,6 +405,40 @@ add_attribute(request, type, (unsigned char *) &value, sizeof(int)); } +static void add_nas_ip_address(AUTH_HDR *request, char *hostname) { + struct addrinfo hints; + struct addrinfo *ai_start; + struct addrinfo *ai; + int v4seen = 0, v6seen = 0; + int r; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_ADDRCONFIG; + + r = getaddrinfo(hostname, NULL, &hints, &ai_start); + if (r != 0) + return; + + ai = ai_start; + while (ai != NULL) { + if (!v4seen && ai->ai_family == AF_INET) { + v4seen = 1; + r = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr; + add_int_attribute(request, PW_NAS_IP_ADDRESS, ntohl(r)); + } + if (!v6seen && ai->ai_family == AF_INET6) { + v6seen = 1; + add_attribute(request, PW_NAS_IPV6_ADDRESS, + (unsigned char *) &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr, 16); + } + ai = ai->ai_next; + } + + freeaddrinfo(ai_start); +} + /* * Add a RADIUS password attribute to the packet. Some magic is done here. * @@ -528,6 +463,9 @@ length = MAXPASS; } + memcpy(hashed, password, length); + memset(hashed + length, 0, sizeof(hashed) - length); + if (length == 0) { length = AUTH_PASS_LEN; /* 0 maps to 16 */ } if ((length & (AUTH_PASS_LEN - 1)) != 0) { @@ -535,9 +473,6 @@ 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) { @@ -583,115 +518,267 @@ next = server->next; _pam_drop(server->hostname); _pam_forget(server->secret); + if (server->sockfd != -1) + close(server->sockfd); + if (server->sockfd6 != -1) + close(server->sockfd6); _pam_drop(server); server = next; } } +static int initialize_sockets(int *sockfd, int *sockfd6, struct sockaddr_storage *salocal4, struct sockaddr_storage *salocal6, char *vrf) +{ + /* open a socket. Dies if it fails */ + *sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (*sockfd < 0) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); + _pam_log(LOG_ERR, "Failed to open RADIUS socket: %s\n", error_string); + return -1; + } + +#ifndef HAVE_POLL_H + if (*sockfd >= FD_SETSIZE) { + _pam_log(LOG_ERR, "Unusable socket, FD is larger than %d\n", FD_SETSIZE); + return -1; + } +#endif + + if (vrf && vrf[0]) { +#ifdef SO_BINDTODEVICE + int r = setsockopt(*sockfd, SOL_SOCKET, SO_BINDTODEVICE, vrf, strlen(vrf)); + if (r != 0) { + _pam_log(LOG_ERR, "Failed bind to %s: %s", vrf, strerror(errno)); + return -1; + } +#else + _pam_log(LOG_ERR, "No SO_BINDTODEVICE, unable to bind to: %s", vrf); + return -1; +#endif + } + + /* set up the local end of the socket communications */ + if (bind(*sockfd, (struct sockaddr *)salocal4, sizeof (struct sockaddr_in)) < 0) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); + _pam_log(LOG_ERR, "Failed binding to port: %s", error_string); + return -1; + } + + /* open a IPv6 socket. Dies if it fails */ + *sockfd6 = socket(AF_INET6, SOCK_DGRAM, 0); + if (*sockfd6 < 0) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); + _pam_log(LOG_ERR, "Failed to open RADIUS IPv6 socket: %s\n", error_string); + return -1; + } +#ifndef HAVE_POLL_H + if (*sockfd6 >= FD_SETSIZE) { + _pam_log(LOG_ERR, "Unusable socket, FD is larger than %d\n", FD_SETSIZE); + return -1; + } +#endif + + if (vrf && vrf[0]) { +#ifdef SO_BINDTODEVICE + int r = setsockopt(*sockfd6, SOL_SOCKET, SO_BINDTODEVICE, vrf, strlen(vrf)); + if (r != 0) { + _pam_log(LOG_ERR, "Failed bind to %s: %s", vrf, strerror(errno)); + return -1; + } +#else + _pam_log(LOG_ERR, "No SO_BINDTODEVICE, unable to bind to: %s", vrf); + return -1; +#endif + } + + /* set up the local end of the socket communications */ + if (bind(*sockfd6, (struct sockaddr *)salocal6, sizeof (struct sockaddr_in6)) < 0) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); + _pam_log(LOG_ERR, "Failed binding to IPv6 port: %s", error_string); + return -1; + } + + return 0; +} + /* * allocate and open a local port for communication with the RADIUS * server */ static int initialize(radius_conf_t *conf, int accounting) { - struct sockaddr salocal; + struct sockaddr_storage salocal; + struct sockaddr_storage salocal4; + struct sockaddr_storage salocal6; 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; + FILE *fp; + radius_server_t *server, **last; int timeout; int line = 0; + char src_ip[MAX_IP_LEN]; + int valid_src_ip; + char vrf[IFNAMSIZ]; + + memset(&salocal4, 0, sizeof(salocal4)); + memset(&salocal6, 0, sizeof(salocal6)); + ((struct sockaddr *)&salocal4)->sa_family = AF_INET; + ((struct sockaddr *)&salocal6)->sa_family = AF_INET6; + conf->sockfd = -1; + conf->sockfd6 = -1; /* the first time around, read the configuration file */ - if ((fserver = fopen (conf_file, "r")) == (FILE*)NULL) { + fp = fopen (conf->conf_file, "r"); + if (!fp) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); _pam_log(LOG_ERR, "Could not open configuration file %s: %s\n", - conf_file, strerror(errno)); + conf->conf_file, error_string); return PAM_ABORT; } - while (!feof(fserver) && (fgets (buffer, sizeof(buffer), fserver) != (char*) NULL) && (!ferror(fserver))) { + conf->server = NULL; + last = &conf->server; + + /* + * Read the file + */ + while (!feof(fp) && (fgets (buffer, sizeof(buffer), fp) != NULL) && (!ferror(fp))) { line++; p = buffer; /* - * Skip blank lines and whitespace + * Skip whitespace */ - while (*p && ((*p == ' ') || (*p == '\t') || (*p == '\r') || (*p == '\n'))) { - p++; - } + while ((*p == ' ') || (*p == '\t')) p++; /* - * Nothing, or just a comment. Ignore the line. + * Skip blank lines and comments. */ - if ((!*p) || (*p == '#')) { - continue; + if ((*p == '\r') || (*p == '\n') || (*p == '#')) continue; + + /* + * Error out if the text is too long. + */ + if (!*p) { + _pam_log(LOG_ERR, "ERROR reading %s, line %d: Line too long\n", + conf->conf_file, line); + break; } + /* + * Initialize the optional variables. + */ timeout = 3; - if (sscanf(p, "%s %s %d", hostname, secret, &timeout) < 2) { + src_ip[0] = 0; + vrf[0] = 0; + + /* + * Scan the line for data. + */ + if (sscanf(p, "%s %s %d %s %s", hostname, secret, &timeout, src_ip, vrf) < 2) { _pam_log(LOG_ERR, "ERROR reading %s, line %d: Could not read hostname or secret\n", - conf_file, line); + conf->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 */ - } + /* + * Fill in the relevant fields. + */ + server = malloc(sizeof(radius_server_t)); + memset(server, 0, sizeof(*server)); + *last = server; + server->next = NULL; + last = &server->next; + + /* sometime later do memory checks here */ + server->hostname = strdup(hostname); + server->secret = strdup(secret); + server->accounting = accounting; - /* sometime later do memory checks here */ - server->hostname = strdup(hostname); - server->secret = strdup(secret); - server->accounting = accounting; - server->port = 0; + /* + * Clamp the timeouts to reasonable values. + */ + if (timeout < 3) { + server->timeout = 3; + } else if (timeout > 60) { + server->timeout = 60; + } else { + server->timeout = timeout; + } - if ((timeout < 1) || (timeout > 60)) { - server->timeout = 3; - } else { - server->timeout = timeout; + server->sockfd = -1; + server->sockfd6 = -1; + + /* + * No source IP for this socket, it uses the + * global one. + */ + if (!src_ip[0]) continue; + + memset(&salocal4, 0, sizeof(salocal4)); + memset(&salocal6, 0, sizeof(salocal6)); + ((struct sockaddr *)&salocal4)->sa_family = AF_INET; + ((struct sockaddr *)&salocal6)->sa_family = AF_INET6; + + valid_src_ip = -1; + vrf[IFNAMSIZ - 1] = 0; + + memset(&salocal, 0, sizeof(salocal)); + valid_src_ip = get_ipaddr(src_ip, (struct sockaddr *)&salocal, NULL); + if (valid_src_ip == 0) { + switch (salocal.ss_family) { + case AF_INET: + memcpy(&salocal4, &salocal, sizeof(salocal)); + break; + case AF_INET6: + memcpy(&salocal6, &salocal, sizeof(salocal)); + break; } - 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; + if (valid_src_ip == 0 || vrf[0]) { + if (initialize_sockets(&server->sockfd, &server->sockfd6, &salocal4, &salocal6, vrf) != 0) { + goto error; + } + } } + fclose(fp); - /* 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; + if (!conf->server) { /* no server found, die a horrible death */ + _pam_log(LOG_ERR, "No RADIUS server found in configuration file %s\n", + conf->conf_file); + goto error; } - /* 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; - s_in->sin_port = 0; - + /* + * Open the global sockets. + */ + memset(&salocal4, 0, sizeof(salocal4)); + memset(&salocal6, 0, sizeof(salocal6)); + ((struct sockaddr *)&salocal4)->sa_family = AF_INET; + ((struct sockaddr *)&salocal6)->sa_family = AF_INET6; - if (bind(conf->sockfd, &salocal, sizeof (struct sockaddr_in)) < 0) { - _pam_log(LOG_ERR, "Failed binding to port: %s", strerror(errno)); - close(conf->sockfd); - return PAM_AUTHINFO_UNAVAIL; + if (initialize_sockets(&conf->sockfd, &conf->sockfd6, &salocal4, &salocal6, NULL) != 0) { + goto error; } return PAM_SUCCESS; + +error: + if (conf->sockfd != -1) + close(conf->sockfd); + if (conf->sockfd6 != -1) + close(conf->sockfd6); + cleanup(conf->server); + return PAM_AUTHINFO_UNAVAIL; } /* @@ -701,7 +788,6 @@ static void build_radius_packet(AUTH_HDR *request, CONST char *user, CONST char *password, radius_conf_t *conf) { char hostname[256]; - uint32_t ipaddr; hostname[0] = '\0'; gethostname(hostname, sizeof(hostname) - 1); @@ -727,23 +813,8 @@ 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(*(uint32_t *) 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); - } + /* Perhaps add NAS IP Address (and v6 version) */ + add_nas_ip_address(request, hostname); /* There's always a NAS identifier */ if (conf->client_id && *conf->client_id) { @@ -766,18 +837,22 @@ static int talk_radius(radius_conf_t *conf, AUTH_HDR *request, AUTH_HDR *response, char *password, char *old_password, int tries) { - socklen_t salen; int total_length; +#ifdef HAVE_POLL_H + struct pollfd pollfds[1]; +#else fd_set set; +#endif 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; + int sockfd; + socklen_t salen; /* ************************************************************ */ /* Now that we're done building the request, we can send it */ @@ -798,63 +873,81 @@ memset(response, 0, sizeof(AUTH_HDR)); /* only look up IP information as necessary */ - if ((retval = host2server(server)) != PAM_SUCCESS) { + retval = host2server(conf->debug, server); + if (retval != 0) { _pam_log(LOG_ERR, - "Failed looking up IP address for RADIUS server %s (errcode=%d)", - server->hostname, retval); + "Failed looking up IP address for RADIUS server %s (error=%s)", + server->hostname, gai_strerror(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); } + if (server->ip->sa_family == AF_INET) { + sockfd = server->sockfd != -1 ? server->sockfd : conf->sockfd; + } else { + sockfd = server->sockfd6 != -1 ? server->sockfd6 : conf->sockfd6; + } + + total_length = ntohs(request->length); server_tries = tries; send: + if (server->ip->sa_family == AF_INET) { + salen = sizeof(struct sockaddr_in); + } else { + salen = sizeof(struct sockaddr_in6); + } + /* send the packet */ - if (sendto(conf->sockfd, (char *) request, total_length, 0, - &saremote, sizeof(struct sockaddr_in)) < 0) { + if (sendto(sockfd, (char *) request, total_length, 0, + server->ip, salen) < 0) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); _pam_log(LOG_ERR, "Error sending RADIUS packet to server %s: %s", - server->hostname, strerror(errno)); + server->hostname, error_string); 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); + + tv.tv_sec = server->timeout; /* wait for the specified time */ + tv.tv_usec = 0; end = now + tv.tv_sec; - /* loop, waiting for the select to return data */ +#ifdef HAVE_POLL_H + pollfds[0].fd = sockfd; /* wait only for the RADIUS UDP socket */ + pollfds[0].events = POLLIN; /* wait for data to read */ +#else + FD_ZERO(&set); /* clear out the set */ + FD_SET(sockfd, &set); /* wait only for the RADIUS UDP socket */ +#endif + + /* loop, waiting for the network to return data */ ok = TRUE; while (ok) { - rcode = select(conf->sockfd + 1, &set, NULL, NULL, &tv); +#ifdef HAVE_POLL_H + rcode = poll((struct pollfd *) &pollfds, 1, tv.tv_sec * 1000); +#else + rcode = select(sockfd + 1, &set, NULL, NULL, &tv); +#endif - /* select timed out */ + /* 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 */ + break; /* exit from the loop */ } else if (rcode < 0) { - /* select had an error */ + /* poll returned an error */ if (errno == EINTR) { /* we were interrupted */ time(&now); @@ -863,29 +956,36 @@ server->hostname); if (--server_tries) goto send; ok = FALSE; - break; /* exit from the select loop */ + break; /* exit from the loop */ } tv.tv_sec = end - now; - if (tv.tv_sec == 0) { /* keep waiting */ + if (tv.tv_sec == 0) { /* keep waiting */ tv.tv_sec = 1; } - } else { /* not an interrupt, it was a real error */ + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); _pam_log(LOG_ERR, "Error waiting for response from RADIUS server %s: %s", - server->hostname, strerror(errno)); + server->hostname, error_string); ok = FALSE; break; } - /* the select returned OK */ - } else if (FD_ISSET(conf->sockfd, &set)) { + /* the call returned OK */ +#ifdef HAVE_POLL_H + } else if (pollfds[0].revents & POLLIN) { +#else + } else if (FD_ISSET(sockfd, &set)) { +#endif /* try to receive some data */ - if ((total_length = recvfrom(conf->sockfd, (void *) response, BUFFER_SIZE, - 0, &saremote, &salen)) < 0) { + if ((total_length = recvfrom(sockfd, (void *) response, BUFFER_SIZE, + 0, NULL, NULL)) < 0) { + char error_string[BUFFER_SIZE]; + get_error_string(errno, error_string, sizeof(error_string)); _pam_log(LOG_ERR, "error reading RADIUS packet from server %s: %s", - server->hostname, strerror(errno)); + server->hostname, error_string); ok = FALSE; break; @@ -940,14 +1040,14 @@ } /* - * Whew! The select is done. It hasn't timed out, or errored out. + * Whew! The poll 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 + * NOW, we can skip out of the loop, and process the packet */ break; } - /* otherwise, we've got data on another descriptor, keep select'ing */ + /* otherwise, we've got data on another descriptor, keep checking the network */ } /* go to the next server if this one didn't respond */ @@ -985,7 +1085,6 @@ /* 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; } } @@ -1066,6 +1165,7 @@ CONST char *rhost; char *resp2challenge = NULL; int ctrl; + int debug; int retval = PAM_AUTH_ERR; int num_challenge = 0; @@ -1076,6 +1176,7 @@ radius_conf_t config; ctrl = _pam_parse(argc, argv, &config); + debug = config.debug; /* grab the user name */ retval = pam_get_user(pamh, &user, NULL); @@ -1123,7 +1224,7 @@ /* 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; } +#define PAM_FAIL_CHECK if (retval != PAM_SUCCESS) {goto do_next; } /* build and initialize the RADIUS packet */ request->code = PW_AUTHENTICATION_REQUEST; @@ -1146,7 +1247,7 @@ if (!password) { if (ctrl & PAM_USE_FIRST_PASS) { retval = PAM_AUTH_ERR; /* use one pass only, stopping if it fails */ - goto error; + goto do_next; } /* check to see if we send a NULL password the first time around */ @@ -1154,6 +1255,8 @@ retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, config.prompt, &password); PAM_FAIL_CHECK; + } else { + password = strdup(""); } } /* end of password == NULL */ @@ -1189,8 +1292,9 @@ * challenges as we receive. */ while (response->code == PW_ACCESS_CHALLENGE) { - attribute_t *a_state, *a_reply; + attribute_t *a_state, *a_reply, *a_prompt; char challenge[BUFFER_SIZE]; + int prompt; /* Now we do a bit more work: challenge the user, and get a response */ if (((a_state = find_attribute(response, PW_STATE)) == NULL) || @@ -1198,7 +1302,7 @@ /* 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; + goto do_next; } /* @@ -1207,14 +1311,26 @@ 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; + goto do_next; } 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); + /* It's full challenge-response, default to echo on, unless the server wants it off */ + prompt = PAM_PROMPT_ECHO_ON; + if (config.prompt_attribute) { + if((a_prompt = find_attribute(response, PW_PROMPT)) != NULL){ + uint32_t prompt_val_net = 0; + uint32_t prompt_val = 0; + memcpy((void *)&prompt_val_net, (void *) a_prompt->data, sizeof(uint32_t)); + prompt_val = ntohl(prompt_val_net); + DPRINT(LOG_DEBUG, "Got Prompt=%d",prompt_val); + if(!prompt_val) prompt=PAM_PROMPT_ECHO_OFF; + } + } + + retval = rad_converse(pamh, prompt, challenge, &resp2challenge); PAM_FAIL_CHECK; /* now that we've got a response, build a new radius packet */ @@ -1251,19 +1367,82 @@ /* Whew! Done the pasword checks, look for an authentication acknowledge */ if (response->code == PW_AUTHENTICATION_ACK) { retval = PAM_SUCCESS; + + /* Read Management-Privilege-Level attribute from the response */ + /* RFC 5607: + * The Management-Privilege-Level (136) Attribute indicates the integer- + * valued privilege level to be assigned for management access for the + * authenticated user. Many NASes provide the notion of differentiated + * management privilege levels denoted by an integer value. The + * specific access rights conferred by each value are implementation + * dependent. It MAY be used in both Access-Request and Access-Accept + * packets. + + * The management access level indicated in this attribute, received in + * an Access-Accept packet, MUST be applied to the session authorized by + * the Access-Accept. If the NAS supports this attribute, but the + * privilege level is unknown, the NAS MUST treat the Access-Accept + * packet as if it had been an Access-Reject. + */ + + if(config.privilege_level) { + char priv[21]; + attribute_t *a_mpl; + int val; + + if ((a_mpl = find_attribute(response, PW_MANAGEMENT_PRIVILEGE_LEVEL)) == NULL) { + _pam_log(LOG_ERR, "RADIUS Access-Accept received with Management-Privilege-Level missing"); + goto do_next; + } + + if (a_mpl->length != 6) { + _pam_log(LOG_ERR, "RADIUS Access-Accept received with invalid Management-Privilege-Level attribute"); + goto do_next; + } + + val = ntohl(*((int *)a_mpl->data)); + sprintf(priv, "Privilege=%d", val); + + /* Save Management-Privilege-Level value in PAM environment variable 'Privilige' */ + retval = pam_putenv(pamh, priv); + if(retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "unable to set PAM environment variable : Privilege"); + goto do_next; + } + } + + attribute_t *attr_fip; + if ((attr_fip = find_attribute(response, PW_FRAMED_ADDRESS))) { + char frameip[100]; + struct in_addr ip_addr; + + ip_addr.s_addr = *(int*) attr_fip->data; + + snprintf(frameip, sizeof(frameip), "Framed-IP-Address=%s", inet_ntoa(ip_addr)); + retval = pam_putenv(pamh, frameip); + if(retval != PAM_SUCCESS) { + _pam_log(LOG_ERR, "unable to set PAM environment variable : Framed-IP-Address"); + } + else { + _pam_log(LOG_DEBUG, "Set PAM environment variable : %s", frameip); + } + } + } 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); - } +do_next: + /* If there was a password pass it to the next layer */ + if (password && *password) { + pam_set_item(pamh, PAM_AUTHTOK, password); } DPRINT(LOG_DEBUG, "authentication %s", retval==PAM_SUCCESS ? "succeeded":"failed"); close(config.sockfd); + if (config.sockfd6 >= 0) + close(config.sockfd6); cleanup(config.server); _pam_forget(password); _pam_forget(resp2challenge); @@ -1297,7 +1476,7 @@ static int pam_private_session(pam_handle_t *pamh, int flags, int argc, CONST char **argv, int status) { CONST char *user; - int ctrl; + CONST char *rhost; int retval = PAM_AUTH_ERR; char recv_buffer[4096]; @@ -1306,7 +1485,7 @@ AUTH_HDR *response = (AUTH_HDR *) recv_buffer; radius_conf_t config; - ctrl = _pam_parse(argc, argv, &config); + (void) _pam_parse(argc, argv, &config); /* grab the user name */ retval = pam_get_user(pamh, &user, NULL); @@ -1352,9 +1531,28 @@ add_int_attribute(request, PW_ACCT_AUTHENTIC, PW_AUTH_RADIUS); if (status == PW_STATUS_START) { - session_time = time(NULL); + time_t *session_time = malloc(sizeof(time_t)); + time(session_time); + pam_set_data(pamh, "rad_session_time", (void *) session_time, _int_free); } else { - add_int_attribute(request, PW_ACCT_SESSION_TIME, time(NULL) - session_time); + time_t *session_time; + retval = pam_get_data(pamh, "rad_session_time", (CONST void **) &session_time); + PAM_FAIL_CHECK; + + add_int_attribute(request, PW_ACCT_SESSION_TIME, time(NULL) - *session_time); + } + + /* + * 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)); } retval = talk_radius(&config, request, response, NULL, NULL, 1); @@ -1371,6 +1569,8 @@ error: close(config.sockfd); + if (config.sockfd6 >= 0) + close(config.sockfd6); cleanup(config.server); return retval; @@ -1605,6 +1805,8 @@ } close(config.sockfd); + if (config.sockfd6 >= 0) + close(config.sockfd6); cleanup(config.server); _pam_forget(password); diff -Nru libpam-radius-auth-1.4.0/src/pam_radius_auth.h libpam-radius-auth-2.0.0/src/pam_radius_auth.h --- libpam-radius-auth-1.4.0/src/pam_radius_auth.h 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/src/pam_radius_auth.h 2020-07-21 20:02:24.000000000 +0000 @@ -3,6 +3,9 @@ #include "config.h" +#include +#include +#include #include #include #include @@ -20,6 +23,17 @@ #include #include #include +#include + +#if defined(HAVE_LINUX_IF_H) +#include +#else +#define IFNAMSIZ 16 /* fallback to current value */ +#endif + +#ifdef HAVE_POLL_H +#include +#endif #if defined(HAVE_SECURITY_PAM_APPL_H) # include @@ -43,47 +57,13 @@ #define MAXPROMPT 33 /* max prompt length, including '\0' */ #define DEFAULT_PROMPT "Password" /* default prompt, without the ': ' */ -/************************************************************************* - * 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; - uint16_t 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 force_prompt; - int max_challenge; - int sockfd; - int debug; - char prompt[MAXPROMPT]; -} radius_conf_t; - /************************************************************************* * Platform specific defines *************************************************************************/ #ifndef CONST -# if defined(__sun) || defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) +# if defined(__sun) || defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) /* * On older versions of Solaris, you may have to change this to: * #define CONST @@ -119,6 +99,9 @@ #define PAM_RUSER_ARG 16 +/* buffer size for IP address in string form */ +#define MAX_IP_LEN 16 + /* Module defines */ #ifndef BUFFER_SIZE #define BUFFER_SIZE 1024 @@ -139,4 +122,46 @@ #define TRUE !FALSE #endif + +/************************************************************************* + * 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 sockaddr_storage ip_storage; + struct sockaddr *ip; + char *hostname; + char *secret; + int timeout; + int accounting; + int sockfd; + int sockfd6; + char vrf[IFNAMSIZ]; +} radius_server_t; + +typedef struct radius_conf_t { + radius_server_t *server; + int retries; + int localifdown; + char *client_id; + int accounting_bug; + int force_prompt; + int max_challenge; + int sockfd; + int sockfd6; + int debug; + CONST char *conf_file; + char prompt[MAXPROMPT]; + int prompt_attribute; + int privilege_level; +} radius_conf_t; + #endif /* PAM_RADIUS_H */ diff -Nru libpam-radius-auth-1.4.0/src/radius.h libpam-radius-auth-2.0.0/src/radius.h --- libpam-radius-auth-1.4.0/src/radius.h 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/src/radius.h 2020-07-21 20:02:24.000000000 +0000 @@ -119,7 +119,10 @@ #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 */ +#define PW_PROMPT 76 /* integer */ +#define PW_MANAGEMENT_PRIVILEGE_LEVEL 136 /* integer */ + +#define PW_NAS_IPV6_ADDRESS 95 /* octets */ /* * INTEGER TRANSLATIONS diff -Nru libpam-radius-auth-1.4.0/USAGE libpam-radius-auth-2.0.0/USAGE --- libpam-radius-auth-1.4.0/USAGE 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/USAGE 2020-07-21 20:02:24.000000000 +0000 @@ -100,5 +100,10 @@ may request. This is a workaround for broken servers and disabled by default. +prompt_attribute - Enable honoring of Prompt attribute sent from server for + challenge-response to enable/disable of echoing of user + input. Without this option all user input during + challenge-response will be echoed. See RFC2869 Section 5.10 + --------------------------------------------------------------------------- diff -Nru libpam-radius-auth-1.4.0/VERSION libpam-radius-auth-2.0.0/VERSION --- libpam-radius-auth-1.4.0/VERSION 2014-12-17 22:00:59.000000000 +0000 +++ libpam-radius-auth-2.0.0/VERSION 2020-07-21 20:02:24.000000000 +0000 @@ -1 +1 @@ -1.4.0 +2.0.0