diff --git a/files/build_templates/sonic_debian_extension.j2 b/files/build_templates/sonic_debian_extension.j2 index d472fe774ee1..0a50dbbc612c 100644 --- a/files/build_templates/sonic_debian_extension.j2 +++ b/files/build_templates/sonic_debian_extension.j2 @@ -291,6 +291,16 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-tacplus_*.deb || \ sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove tacplus sudo sed -i -e '/^passwd/s/ tacplus//' $FILESYSTEM_ROOT/etc/nsswitch.conf +# Install pam-radius-auth and nss-radius +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libpam-radius-auth_*.deb || \ + sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f +sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-radius_*.deb || \ + sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f +# Disable radius by default +# radius does not have any profiles +#sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove radius tacplus +sudo sed -i -e '/^passwd/s/ radius//' $FILESYSTEM_ROOT/etc/nsswitch.conf + # Install a custom version of kdump-tools (and its dependencies via 'apt-get -y install -f') if [[ $CONFIGURED_ARCH == amd64 ]]; then sudo DEBIAN_FRONTEND=noninteractive dpkg --root=$FILESYSTEM_ROOT -i $debs_path/kdump-tools_*.deb || \ diff --git a/rules/radius.dep b/rules/radius.dep new file mode 100644 index 000000000000..3a6d5c592fb5 --- /dev/null +++ b/rules/radius.dep @@ -0,0 +1,13 @@ + +SPATH := $($(LIBPAM_RADIUS)_SRC_PATH) +DEP_FILES := $(SONIC_COMMON_FILES_LIST) rules/radius.mk rules/radius.dep +DEP_FILES += $(SONIC_COMMON_BASE_FILES_LIST) +DEP_FILES += $(shell git ls-files $(SPATH)) + +$(LIBPAM_RADIUS)_CACHE_MODE := GIT_CONTENT_SHA +$(LIBPAM_RADIUS)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(LIBPAM_RADIUS)_DEP_FILES := $(DEP_FILES) + +$(LIBNSS_RADIUS)_CACHE_MODE := GIT_CONTENT_SHA +$(LIBNSS_RADIUS)_DEP_FLAGS := $(SONIC_COMMON_FLAGS_LIST) +$(LIBNSS_RADIUS)_DEP_FILES := $(DEP_FILES) diff --git a/rules/radius.mk b/rules/radius.mk new file mode 100644 index 000000000000..643acfd497b2 --- /dev/null +++ b/rules/radius.mk @@ -0,0 +1,24 @@ +# libpam-radius-auth packages + +PAM_RADIUS_VERSION = 1.4.1-1 + +export PAM_RADIUS_VERSION + +LIBPAM_RADIUS = libpam-radius-auth_$(PAM_RADIUS_VERSION)_amd64.deb +$(LIBPAM_RADIUS)_SRC_PATH = $(SRC_PATH)/radius/pam +SONIC_MAKE_DEBS += $(LIBPAM_RADIUS) + +SONIC_STRETCH_DEBS += $(LIBPAM_RADIUS) + +# libnss-radius packages + +NSS_RADIUS_VERSION = 1.0.1-1 + +export NSS_RADIUS_VERSION + +LIBNSS_RADIUS = libnss-radius_$(NSS_RADIUS_VERSION)_amd64.deb +$(LIBNSS_RADIUS)_SRC_PATH = $(SRC_PATH)/radius/nss +SONIC_MAKE_DEBS += $(LIBNSS_RADIUS) + +SONIC_STRETCH_DEBS += $(LIBNSS_RADIUS) + diff --git a/slave.mk b/slave.mk index 3db689e5e576..518d337f5240 100644 --- a/slave.mk +++ b/slave.mk @@ -849,6 +849,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \ $(IFUPDOWN2) \ $(KDUMP_TOOLS) \ $(NTP) \ + $(LIBPAM_RADIUS) \ + $(LIBNSS_RADIUS) \ $(LIBPAM_TACPLUS) \ $(LIBNSS_TACPLUS) \ $(MONIT) \ diff --git a/src/radius/nss/Makefile b/src/radius/nss/Makefile new file mode 100644 index 000000000000..6559ae585edb --- /dev/null +++ b/src/radius/nss/Makefile @@ -0,0 +1,23 @@ +.ONESHELL: +SHELL = /bin/bash +.SHELLFLAGS += -e + +MAIN_TARGET = libnss-radius_$(NSS_RADIUS_VERSION)_amd64.deb + +$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : + pushd ./libnss-radius + + make clean + -rm -rf debian + -rm -rf patches + cp -r ../debian . + cp -r ../patches . + + # Apply patch (if any) + + dpkg-buildpackage -rfakeroot -b -us -uc + popd + + mv $(DERIVED_TARGETS) $* $(DEST)/ + +$(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET) diff --git a/src/radius/nss/debian/README.Debian b/src/radius/nss/debian/README.Debian new file mode 100644 index 000000000000..894b4ef8c049 --- /dev/null +++ b/src/radius/nss/debian/README.Debian @@ -0,0 +1,5 @@ +libnss-radius for Debian + +The Debian version of the libnss-radius package. + + -- Arun Barboza <29963827+a-barboza@users.noreply.github.com> Tue, 24 Sep 2019 00:20:55 +0000 diff --git a/src/radius/nss/debian/changelog b/src/radius/nss/debian/changelog new file mode 100644 index 000000000000..ee1cc9150468 --- /dev/null +++ b/src/radius/nss/debian/changelog @@ -0,0 +1,6 @@ +libnss-radius (1.0.1-1) unstable; urgency=low + + * Initial release. NSS lookups for RADIUS users with cached Management + Privilege Level (MPL) attribute. + + -- Arun Barboza <29963827+a-barboza@users.noreply.github.com> Tue, 24 Sep 2019 00:20:55 +0000 diff --git a/src/radius/nss/debian/compat b/src/radius/nss/debian/compat new file mode 100644 index 000000000000..ec635144f600 --- /dev/null +++ b/src/radius/nss/debian/compat @@ -0,0 +1 @@ +9 diff --git a/src/radius/nss/debian/control b/src/radius/nss/debian/control new file mode 100644 index 000000000000..a7903b0571e2 --- /dev/null +++ b/src/radius/nss/debian/control @@ -0,0 +1,17 @@ +Source: libnss-radius +Section: libs +Priority: optional +Maintainer: Arun Barboza <29963827+a-barboza@users.noreply.github.com> +Build-Depends: debhelper (>=9) +Standards-Version: 3.9.6 +Homepage: http://www.broadcom.com + +Package: libnss-radius +Section: libs +Architecture: any +Multi-Arch: same +Pre-Depends: ${misc:Pre-Depends} +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: NSS module for RADIUS authentication absent local account. + NSS lookups for RADIUS authenticated users using the Management Privilege + Level (MPL) cached attribute. diff --git a/src/radius/nss/debian/copyright b/src/radius/nss/debian/copyright new file mode 100644 index 000000000000..f56de29d49bc --- /dev/null +++ b/src/radius/nss/debian/copyright @@ -0,0 +1,21 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: libnss-radius +Source: http://www.broadcom.com + +Files: * +Copyright: 2019 Broadcom. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +License: Apache + Copyright 2019 Broadcom. All rights reserved. + The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + . + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/radius/nss/debian/rules b/src/radius/nss/debian/rules new file mode 100755 index 000000000000..dca10d171750 --- /dev/null +++ b/src/radius/nss/debian/rules @@ -0,0 +1,15 @@ +#!/usr/bin/make -f +# You must remove unused comment lines for the released package. +#export DH_VERBOSE = 1 +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + +%: + dh $@ + +#override_dh_auto_install: +# dh_auto_install -- prefix=/usr + +#override_dh_install: +# dh_install --list-missing -X.pyc -X.pyo diff --git a/src/radius/nss/debian/watch b/src/radius/nss/debian/watch new file mode 100644 index 000000000000..76575dc53be5 --- /dev/null +++ b/src/radius/nss/debian/watch @@ -0,0 +1,2 @@ +# You must remove unused comment lines for the released package. +version=3 diff --git a/src/radius/nss/libnss-radius/.gitignore b/src/radius/nss/libnss-radius/.gitignore new file mode 100644 index 000000000000..62fb1dddb8ba --- /dev/null +++ b/src/radius/nss/libnss-radius/.gitignore @@ -0,0 +1,6 @@ +cache_radius +libnss_radius.so.2 +test_cache_radius +test_nss_radius +debian +patches diff --git a/src/radius/nss/libnss-radius/LICENSE b/src/radius/nss/libnss-radius/LICENSE new file mode 100644 index 000000000000..00d5b1b9fabe --- /dev/null +++ b/src/radius/nss/libnss-radius/LICENSE @@ -0,0 +1,14 @@ +Copyright 2019 Broadcom. All rights reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/src/radius/nss/libnss-radius/Makefile b/src/radius/nss/libnss-radius/Makefile new file mode 100644 index 000000000000..adbee2941dc8 --- /dev/null +++ b/src/radius/nss/libnss-radius/Makefile @@ -0,0 +1,59 @@ +####################################################################### +# +# Copyright 2019 Broadcom. All rights reserved. +# The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +# +####################################################################### + +# +# Makefile for libnss-radius +# + +TARGETS = libnss_radius.so.2 cache_radius +COMMON_INCLUDE = nss_radius_common.h +COMMON_SOURCE = nss_radius_common.c +LIBNSS_SOURCE = nss_radius.c $(COMMON_SOURCE) +CACHE_SOURCE = cache_radius.c $(COMMON_SOURCE) + +# For now place the multiarch flag here +# Eventually this needs to be move to the debian packaging +#moduledir = $(prefix)/lib/x86_64-linux-gnu +moduledir = $(prefix)/lib/$(DEB_HOST_MULTIARCH) + + +all: $(TARGETS) + +libnss_radius.so.2: $(LIBNSS_SOURCE) $(COMMON_INCLUDE) + $(CC) $(CFLAGS) $(LDFLAGS) -fPIC -Wall -shared -o libnss_radius.so.2 \ + -Wl,-soname,libnss_radius.so.2 -Wl,--version-script=libnss_radius_vs.txt $(LIBNSS_SOURCE) + +cache_radius: $(CACHE_SOURCE) $(COMMON_INCLUDE) + $(CC) $(CFLAGS) $(LDFLAGS) -o cache_radius $(CACHE_SOURCE) + +clean: + -rm -f $(TARGETS) + -rm -f test_nss_radius test_cache_radius + +install: libnss_radius.so.2 cache_radius + install -m 0644 -D libnss_radius.so.2 \ + $(DESTDIR)$(moduledir)/libnss_radius.so.2 + install -m 0755 -D cache_radius \ + $(DESTDIR)$(prefix)/usr/sbin/cache_radius + install -m 0755 -d $(DESTDIR)$(prefix)/etc/pam_radius_auth.d/ + +distclean: clean + +uninstall: + -rm -f $(DESTDIR)$(moduledir)/libnss_radius.so.2 + -rm -f $(DESTDIR)$(prefix)/usr/sbin/cache_radius + +test: test_nss_radius.c $(LIBNSS_SOURCE) $(CACHE_SOURCE) \ + $(COMMON_SOURCE) $(COMMON_INCLUDE) + $(CC) $(CFLAGS) $(LDFLAGS) -g -DTEST_RADIUS_NSS -o test_nss_radius \ + $(LIBNSS_SOURCE) test_nss_radius.c + $(CC) $(CFLAGS) $(LDFLAGS) -g -DTEST_RADIUS_NSS -o test_cache_radius \ + $(CACHE_SOURCE) + + +.PHONY: all install clean distclean uninstall test + diff --git a/src/radius/nss/libnss-radius/cache_radius.c b/src/radius/nss/libnss-radius/cache_radius.c new file mode 100644 index 000000000000..e6c06cab5bfa --- /dev/null +++ b/src/radius/nss/libnss-radius/cache_radius.c @@ -0,0 +1,198 @@ +/* +Copyright 2019 Broadcom. All rights reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +*/ + +/* + * cache_radius to be invoked after a successful pam_radius_auth.so + * module invocation as: + * + * auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius + * + * cache_radius -u to be invoked to clear aged unconfirmed users. + * + * auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius -u + * + */ + +#include "nss_radius_common.h" + +static int radius_update_cache_cleanup(int status, mode_t mask, FILE * fp) { + + /* Umask restore. + */ + umask(mask); + + if (fp) + fclose(fp); + + return status; +} + +static int radius_update_cache( char * prog, const char * nam, int mpl) { + + mode_t mask; + char filename[PATH_MAX]; + int written; + struct stat sb; + FILE * fp = NULL; + + /* Umask save, change. + */ + mask = umask(022); + + if (((written = snprintf( filename, sizeof(filename), "%s/%s", + RADIUS_ATTRIBUTE_CACHE_DIR, nam)) + sizeof(RADIUS_ATTR_MPL) + 1) + >= sizeof(filename)) { + syslog( LOG_ERR, "%s: \"%s\": too long.", prog, filename); + return(radius_update_cache_cleanup(STATUS_EINVAL, mask, fp)); + } + + /* Create the directory. + */ + if ((!((stat( RADIUS_CACHE_DIR, &sb) == 0) && S_ISDIR(sb.st_mode)) + && (mkdir( RADIUS_CACHE_DIR, 0755) == -1)) + || (!((stat( RADIUS_ATTRIBUTE_CACHE_DIR, &sb) == 0) && S_ISDIR(sb.st_mode)) + && (mkdir( RADIUS_ATTRIBUTE_CACHE_DIR, 0755) == -1)) + || (!((stat( filename, &sb) == 0) && S_ISDIR(sb.st_mode)) + && (mkdir( filename, 0755) == -1))) { + syslog( LOG_ERR, "%s: \"%s\": mkdir() fails. Or ! dir errno %d", + prog, filename, errno); + return(radius_update_cache_cleanup(STATUS_ENOENT, mask, fp)); + } + + /* Create the file. + */ + strcat( filename, "/"); + strcat( filename, RADIUS_ATTR_MPL); + + if ( ((fp = fopen( filename, "w")) == NULL) + || (fprintf(fp, "%d\n", mpl) < 0)) { + syslog( LOG_ERR, + "%s: \"%s\": fopen(): %p or fprintf() fails. errno %d", prog, + filename, (void *) fp, errno); + return(radius_update_cache_cleanup(STATUS_EPERM, mask, fp)); + } + + syslog(LOG_INFO, "%s: MPL %d updated for user %s", prog, mpl, nam); + + return(radius_update_cache_cleanup(0, mask, fp)); +} + + +static int main_cleanup(int status, RADIUS_NSS_CONF_B * conf, int * pncfd) { + int my_errno = 0; + if (conf) + unparse_nss_config(conf, &my_errno, pncfd); + return status; +} + +int main(int ac, char * av[]) { + + int mpl = 1, cached_mpl = 1; + int status = 0; + int my_errno = 0; + char * user = NULL, * privilege; + RADIUS_NSS_CONF_B radius_nss_conf, * conf = & radius_nss_conf; + RADIUS_NSS_MPL * rnm; + char file_buf[RADIUS_MAX_NSS_CONF_SZ]; + int ncfd = -1; + int no_clear_unconfirmed = 0; + int clear_unconfirmed_limit = 0; + int refresh_user = 0; + char buf[BUFLEN]; + struct passwd pw, *result = NULL; + + /* Set the logging parameters. + */ + + openlog(av[0], LOG_CONS | LOG_PID, LOG_AUTHPRIV); + + /* Parse Command-Line options. + */ + + if ((ac == 2) && (strncmp(av[1], "-n", 3) == 0)) { + + no_clear_unconfirmed = 1; + + } else if (ac > 1) { + + syslog(LOG_WARNING, + "%s: Ignoring unknown option:\"%s\"\n", av[0], av[1]); + } + + /* Read environment PAM_USER. + */ + + if ( ((user = getenv("PAM_USER")) == NULL) + || (user[0] == 0)) { + syslog(LOG_WARNING, + "%s: Missing or bad PAM_USER in environment:\"%s\"\n", + av[0], user ? user : "(null)"); + exit(main_cleanup(STATUS_ENOENT, NULL, NULL)); + } + + /* Read environment Privilege. + */ + + if ((privilege = getenv("Privilege")) != NULL) { + mpl = atoi(privilege); + if (!((RADIUS_MIN_MPL <= mpl) && (mpl <= RADIUS_MAX_MPL))) { + syslog(LOG_WARNING, "%s: Invalid Management-Privilege-Level %d\n", + av[0], mpl); + mpl = 1; + } + } else { + syslog(LOG_INFO, "%s: Missing or bad Privilege in environment:\"%s\"\n", + av[0], privilege ? privilege : ""); + } + + parse_nss_config(conf, av[0], file_buf, sizeof(file_buf), &my_errno, &ncfd); + + if ( (radius_lookup_cache( conf->prog, user, &cached_mpl) != 0) + || (cached_mpl != mpl) + || (radius_getpwnam_r(conf->prog, user, &pw, buf, sizeof(buf),&result) + != 0)) { + + radius_update_cache( conf->prog, user, mpl); + refresh_user = 1; + + } + + if (conf->many_to_one) { + + rnm = &((conf->rnm)[mpl-1]); + if (radius_getpwnam_r(conf->prog, rnm->gecos, &pw, buf, sizeof(buf), + &result) != 0) { + radius_create_user(conf, rnm->gecos, mpl, RADIUS_CONFIRMED); + } + + if (mpl != cached_mpl) { + + syslog(LOG_WARNING, "%s: Management-Privilege-Level init/changed:" + " %d --> %d\n", conf->prog, cached_mpl, mpl); + fprintf(stdout, "Management-Privilege-Level inited or changed.\n"); + fprintf(stdout, "Please login again.\n"); + exit(main_cleanup(STATUS_EPERM, conf, &ncfd)); + + } + + } else if (!(conf->many_to_one) && refresh_user) { + + radius_update_user( conf, user, mpl); + + } + + if (!no_clear_unconfirmed && (conf->many_to_one == 0)) { + clear_unconfirmed_limit = conf->unconfirmed_clear_limit; + while(radius_clear_unconfirmed_users(conf) == 0) { + if (clear_unconfirmed_limit-- <= 0) { + syslog(LOG_INFO, "%s: Clear unconfirmed limit %d reached:", + conf->prog, conf->unconfirmed_clear_limit); + break; + } + } + } + + exit(main_cleanup(0, conf, &ncfd)); +} diff --git a/src/radius/nss/libnss-radius/libnss_radius_vs.txt b/src/radius/nss/libnss-radius/libnss_radius_vs.txt new file mode 100644 index 000000000000..e6853137416a --- /dev/null +++ b/src/radius/nss/libnss-radius/libnss_radius_vs.txt @@ -0,0 +1,4 @@ +{ +global: _nss_radius_getpwnam_r; +local: *; +}; diff --git a/src/radius/nss/libnss-radius/nss_radius.c b/src/radius/nss/libnss-radius/nss_radius.c new file mode 100644 index 000000000000..a712cd72f9d1 --- /dev/null +++ b/src/radius/nss/libnss-radius/nss_radius.c @@ -0,0 +1,84 @@ +/* +Copyright 2019 Broadcom. All rights reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +*/ + +/* + * plugin implements getpwnam_r for NSS for RADIUS cached MPL(Management + * Privilege Attribute). + */ + +#include "nss_radius_common.h" + +/* + * NSS entry point for getpwnam(). + */ +enum nss_status _nss_radius_getpwnam_r( const char * nam, struct passwd * pwd, + char * buf, size_t buflen, int * errnop) { + + enum nss_status status = NSS_STATUS_NOTFOUND; + int mpl = 1; + int ncfd = -1; + RADIUS_NSS_CONF_B radius_nss_conf, * conf = &(radius_nss_conf); + RADIUS_NSS_MPL * rnm; + char file_buf[RADIUS_MAX_NSS_CONF_SZ]; + char buffer[BUFLEN]; + struct passwd pw, *res = NULL; + char * prog = "nss"; + + /* Ignore filename completion. + */ + if (!nam || !strcmp(nam, "*") || !pwd || !buf || (buflen == 0)) + return NSS_STATUS_NOTFOUND; + + parse_nss_config(conf, prog, file_buf, sizeof(file_buf), errnop, &ncfd); + + if (radius_lookup_cache(prog, nam, &mpl) == 0) { + + /* The MPL exists for this user in the cache. + */ + + if (conf->debug) + syslog( LOG_DEBUG, "%s: nam: %s", prog, nam); + + rnm = &((conf->rnm)[mpl-1]); + if (conf->many_to_one) { + radius_getpwnam_r(prog, rnm->gecos, &pw, buffer, sizeof(buffer), + &res); + } else if (conf->allow_anonymous) { + radius_create_user(conf, nam, mpl, RADIUS_CONFIRMED); + radius_getpwnam_r(prog, nam, &pw, buffer, sizeof(buffer), &res); + } + + } else if (conf->allow_anonymous && is_sshd_lookup(conf, nam)) { + + /* Could be an sshd doing a getpwnam() before pam_authenticate(). + */ + + rnm = &((conf->rnm)[mpl-1]); + if (conf->many_to_one) { + if (radius_getpwnam_r(prog, rnm->gecos, &pw, buffer, sizeof(buffer), + &res) != 0) { + radius_create_user(conf, rnm->gecos, mpl, RADIUS_CONFIRMED); + radius_getpwnam_r(prog, rnm->gecos, &pw, buffer, sizeof(buffer), + &res); + } + } else { + radius_create_user(conf, nam, mpl, RADIUS_UNCONFIRMED); + radius_getpwnam_r(prog, nam, &pw, buffer, sizeof(buffer), &res); + } + } + + /* Fill the pwd + */ + + if (res) { + status = NSS_STATUS_SUCCESS; + radius_copy_pw(conf, res, nam, pwd, buf, buflen, errnop); + } + + unparse_nss_config(conf, errnop, &ncfd); + + return status; +} + diff --git a/src/radius/nss/libnss-radius/nss_radius_common.c b/src/radius/nss/libnss-radius/nss_radius_common.c new file mode 100644 index 000000000000..652d04ae5c7b --- /dev/null +++ b/src/radius/nss/libnss-radius/nss_radius_common.c @@ -0,0 +1,780 @@ +/* +Copyright 2019 Broadcom. All rights reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +*/ + +/* + * Common code for NSS for RADIUS cached MPL(Management Privilege Attribute). + * Used by both NSS module(uses the cache) and PAM module(updates the cache) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nss_radius_common.h" + +static void dump_rnm(int mpl, RADIUS_NSS_MPL * rnm, char * msg) { + + syslog( LOG_DEBUG, "dump_rnm: %s:" + "mpl %d gid %d groups \"%s\" gecos \"%s\" shell \"%s\"", + (msg ? msg : ""), mpl, rnm->gid, rnm->groups, rnm->gecos, rnm->shell); +} + +static char * parse_line(RADIUS_NSS_CONF_B * conf, char * file_buf, + char ** pscanpos, int * premain) { + + char * line = NULL; + int skipline = 0; + + for( ; (*premain); (*pscanpos)++, (*premain)--) { + if (skipline) { + if ( ((**pscanpos) == '\n') + || ((**pscanpos) == '\r') + || ((**pscanpos) == '\f')) { + skipline = 0; + } + continue; + } else if (line == NULL) { + if (isspace(**pscanpos)) { + continue; + } else if ((**pscanpos) == '#') { + skipline = 1; + } else { + line = *pscanpos; + } + continue; + } else if ( ((**pscanpos) == '\n') + || ((**pscanpos) == '\r') + || ((**pscanpos) == '\f')) { + break; + } + } + + if (line) { + *((*pscanpos)++) = 0; + if (*premain) + (*premain)--; + } + + if (conf->trace) + syslog(LOG_DEBUG, "parse_line: \"%s\"\n", line ? line : ""); + + return line; +} + +static char * parse_token(RADIUS_NSS_CONF_B * conf, char ** pbufpos, + int * premain) { + char * token = NULL; + + for( token = *pbufpos; + (*premain) && (**pbufpos) && ((**pbufpos) != ';') ; + (*pbufpos)++, (*premain)--) + ; /* Empty Body */ + + if (*premain) { + *((*pbufpos)++) = 0; + (*premain)--; + } + + if (conf->trace) + syslog(LOG_DEBUG, "parse_token: \"%s\"\n", token ? token : ""); + + return token; +} + +static int parse_user(RADIUS_NSS_CONF_B * conf, char * line) { + RADIUS_NSS_MPL parse_rnm; + char * token, * bufpos; + int mpl = 0; + int ret = 0, remain; + + bufpos = line; + remain = strlen(line); + memset((char *) &parse_rnm, 0, sizeof(parse_rnm)); + + while (remain && (token = parse_token(conf, &bufpos, &remain))) { + if (strncmp(token, "user_priv=", 10) == 0) { + mpl = atoi(&token[10]); + } else if (strncmp(token, "group=", 6) == 0) { + parse_rnm.groups = &(token[6]); + } else if (strncmp(token, "pw_info=", 8) == 0) { + parse_rnm.gecos = &(token[8]); + } else if (strncmp(token, "uid=", 4) == 0) { + syslog( LOG_WARNING, "%s: Ignoring \"%s\"." + " Using system assigned User ID", RADIUS_NSS_CONF, token); + } else if (strncmp(token, "gid=", 4) == 0) { + parse_rnm.gid = atol(&(token[4])); + } else if (strncmp(token, "dir=", 4) == 0) { + syslog( LOG_WARNING, "%s: Ignoring \"%s\"." + " Using system assigned home directory", + RADIUS_NSS_CONF, token); + } else if (strncmp(token, "shell=", 6) == 0) { + parse_rnm.shell = &(token[6]); + } + } + + if (conf->trace) + dump_rnm(mpl, &parse_rnm, "parse"); + + if ( ((RADIUS_MIN_MPL > mpl) || (mpl > RADIUS_MAX_MPL)) + || (parse_rnm.gid == 0) + || (parse_rnm.groups == NULL) + || (parse_rnm.gecos == NULL) + || (parse_rnm.shell == NULL)) { + + syslog( LOG_WARNING, "%s: Bad user_priv line \"%s\"", RADIUS_NSS_CONF, + line); + ret = 1; + } else { + (conf->rnm)[mpl-1] = parse_rnm; + } + + return ret; +} + +static void init_rnm(RADIUS_NSS_CONF_B * conf) { + + RADIUS_NSS_MPL * rnm = conf->rnm; + + /* Set rnm[0,max_mpl-1]. + */ + + memset((char *) rnm, 0, sizeof(conf->rnm)); + + rnm[0].gid = 999; + rnm[0].groups = "docker"; + rnm[0].gecos = "remote_user"; + rnm[0].shell = "/usr/bin/sonic-launch-shell"; + rnm[RADIUS_MAX_MPL-1].gid = 1000; + rnm[RADIUS_MAX_MPL-1].groups = "admin,sudo,docker"; + rnm[RADIUS_MAX_MPL-1].gecos = "remote_user_su"; + rnm[RADIUS_MAX_MPL-1].shell = "/usr/bin/sonic-launch-shell"; + +} + +int parse_nss_config(RADIUS_NSS_CONF_B * conf, char * prog, + char * file_buf, int file_buf_sz, int * errnop, int * plockfd) { + + /* Slurp the whole file. + */ + int ncfd = -1; + int ret = 0; + int i = 0, remain; + struct stat sb; + char errbuf[128]; + int flags; + char * scanpos, * line; + int use_default_rnm = 1, bad_rnm = 0; + int unconfirmed_disallow = 0; + + memset((char *)conf, 0, sizeof(*conf)); + conf->prog = prog; + conf->allow_anonymous = 1; + conf->unconfirmed_ageout = UNCONFIRMED_AGEOUT_DEFAULT; + conf->unconfirmed_clear_limit = UNCONFIRMED_CLEAR_LIMIT_DEFAULT; + + /* Read the file. + */ + if (((ncfd = open(RADIUS_NSS_CONF, O_RDONLY)) == -1) + || (fstat(ncfd, &sb) == -1) + || (((flags = fcntl(ncfd, F_GETFL, 0)) == -1) && ((flags = 0) != 0)) + || (fcntl(ncfd, F_SETFL, flags | O_NONBLOCK) == -1)) { + + if (errnop) + *errnop = errno; + ret = 1; + errbuf[0] = 0; strerror_r(errno, errbuf, sizeof(errbuf)); + syslog( LOG_WARNING, "%s: %s", prog, errbuf); + goto parse_nss_config_exit; + } + + /* The maximum file size is 1 less than the buffer, to allow space for + * a NULL byte in the case where the last line has no \n, \r, \l char. + * (which could have been substituted with a NULL). + */ + if (sb.st_size >= file_buf_sz) { + syslog( LOG_WARNING, "%s: size greater than %d. Ignoring", + prog, file_buf_sz - 1); + goto parse_nss_config_exit; + } + + if ((i = read(ncfd, file_buf, file_buf_sz)) != sb.st_size) { + syslog( LOG_WARNING, "%s: read %d of %ld. Ignoring", prog, + i, sb.st_size); + goto parse_nss_config_exit; + } + + /* Parse each line from file. + */ + + scanpos = file_buf; + remain = sb.st_size; + while((line = parse_line(conf, file_buf, &scanpos, &remain)) != NULL) { + + if (strncmp(line, "debug=", 6) == 0) { + + if (strncmp(&(line[6]), "on", 2) == 0) { + + /* Handle "debug". + */ + + conf->debug = 1; + + } else if (strncmp(&(line[6]), "trace", 5) == 0) { + + conf->debug = 1; + conf->trace = 1; + + } + + } else if (strncmp(line, "user_priv=", 10) == 0) { + + /* Handle "user_priv" + */ + + use_default_rnm = 0; + if (parse_user(conf, line)) { + bad_rnm = 1; + } + + } else if (strncmp(line, "many_to_one=", 12) == 0) { + + /* Handle "many_to_one" + */ + + if ((strncmp(&(line[12]), "y", 2) == 0) || + (strncmp(&(line[12]), "ye", 3) == 0) || + (strncmp(&(line[12]), "yes", 4) == 0)) { + + conf->many_to_one = 1; + + } else if (strncmp(&(line[12]), "a", 1) == 0) { + + conf->many_to_one = 0; + syslog( LOG_WARNING, "%s: a(nonymous) is same as n(o) option:" + " \"%s\"", prog, line); + + } else if ((strncmp(&(line[12]), "n", 2) == 0) || + (strncmp(&(line[12]), "no", 3) == 0)) { + + conf->many_to_one = 0; + + } else { + + syslog( LOG_WARNING, "%s: Ignorning \"%s\"", prog, line); + + } + + } else if (strncmp(line, "unconfirmed_regexp=", 19) == 0) { + + conf->unconfirmed_regexp = &(line[19]); + + } else if (strncmp(line, "unconfirmed_ageout=", 19) == 0) { + + conf->unconfirmed_ageout = atoi(&(line[19])); + + } else if (strncmp(line, "unconfirmed_disallow=", 21) == 0) { + + if ((strncmp(&(line[21]), "y", 2) == 0) || + (strncmp(&(line[21]), "ye", 3) == 0) || + (strncmp(&(line[21]), "yes", 4) == 0)) { + + unconfirmed_disallow = 1; + + } else if ((strncmp(&(line[21]), "n", 2) == 0) || + (strncmp(&(line[21]), "no", 3) == 0)) { + + unconfirmed_disallow = 0; + + } else { + syslog( LOG_WARNING, "%s: Ignorning \"%s\"", prog, line); + } + + } else { + + syslog( LOG_WARNING, "%s: Ignoring \"%s\"", prog, line); + } + } + + if (unconfirmed_disallow) { + + conf->allow_anonymous = 0; + + } + + +parse_nss_config_exit: + + if (ncfd != -1) { + + if (flock(ncfd, LOCK_EX|LOCK_NB) == 0) { + if (conf->debug) + syslog( LOG_DEBUG, "%s: %d: Unconfirmed: lock success", + prog, (int) getpid()); + } else { + conf->allow_anonymous = 0; + if (conf->debug) + syslog( LOG_DEBUG, "%s: %d: Unconfirmed: locked out", + prog, (int) getpid()); + } + + if (plockfd) { + *plockfd = ncfd; + } else { + close(ncfd); /* This should release the lock, if we placed one */ + } + } + + + /* Fix up rnm. + */ + + if (use_default_rnm || bad_rnm) + init_rnm(conf); + + for ( i = 1; i < RADIUS_MAX_MPL; i++) { + if ((conf->rnm)[i].gecos == NULL) { + (conf->rnm)[i] = (conf->rnm)[i-1]; + } + } + + return ret; +} + +/* Releases any memory. + * Closes any fds. + */ +int unparse_nss_config(RADIUS_NSS_CONF_B * conf, int * errnop, int * plockfd) { + + /* If the caller had a lock in parse_nss_config(), + * we should now release that lock. + */ + if (plockfd && (*plockfd != -1)) { + if (flock(*plockfd, LOCK_UN|LOCK_NB) == 0) { + if (conf->debug) + syslog( LOG_DEBUG, "%s: %d: Unconfirmed: unlock success", + conf->prog, (int) getpid()); + } else { + syslog( LOG_ERR, "%s: %d: Unconfirme: unlock fail", + conf->prog, (int) getpid()); + } + close(*plockfd); + *plockfd = -1; + } + return 0; +} + +static int invoke_popen(RADIUS_NSS_CONF_B * conf, char * cmd) { + FILE * fp; + int status = 0; + + if (conf->debug) + syslog(LOG_DEBUG, "%s:%s", conf->prog, cmd); + + if (((fp = popen(cmd, "r")) == NULL) || (pclose(fp) == -1)) { + syslog(LOG_ERR, "%s: %s: popen()/pclose() failed %p, errno=%d", + conf->prog, cmd, fp, errno); + status = errno; + } + + return status; +} + +static int radius_getpwnam_r_cleanup(int status, FILE * fp) { + if (fp) + fclose(fp); + return status; +} + +int radius_getpwnam_r(char * prog, const char * name, + struct passwd * pwd, char * buf, int buflen, struct passwd ** result) { + + FILE *fp; + int status; + + if ((fp = fopen(ETC_PASSWD, "r")) == NULL) { + syslog(LOG_ERR, "%s: fopen(\"/etc/passwd\") failed\n", prog); + return radius_getpwnam_r_cleanup(STATUS_ENOENT, fp); + } + + while((status = fgetpwent_r(fp, pwd, buf, buflen, result)) == 0) { + if ( (*result) + && (*result)->pw_name + && (strcmp((*result)->pw_name, name) == 0)) { + return radius_getpwnam_r_cleanup(status, fp); + } + } + + syslog(LOG_INFO, "%s: User %s not found in local db: %d\n", prog, name, + status); + + return radius_getpwnam_r_cleanup(status, fp); +} + + +static int radius_update_user_cleanup(int status) { + return status; +} + +int radius_update_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl) { + + char buf[BUFLEN]; + char usermod[4096]; + struct passwd pw, *result = NULL; + RADIUS_NSS_MPL * rnm = NULL; + int written = 0; + int status; + + /* Verify uid is not in the reserved range (<=1000). + */ + + if ((status = radius_getpwnam_r(conf->prog, user, &pw, buf, sizeof(buf), + &result)) != 0) { + + /* /bin/login does NSS after PAM. + */ + status = radius_create_user(conf, user, mpl, RADIUS_CONFIRMED); + return radius_update_user_cleanup(status); + } + + if (result->pw_uid <= 1000) { + syslog(LOG_INFO, "%s: Not changing \"%s\", uid(%d) <= 1000", + conf->prog, user, result->pw_uid); + return radius_update_user_cleanup(STATUS_EPERM); + } + + syslog(LOG_INFO, "%s: Adjusting Supplementary Groups for \"%s\"", + conf->prog, user); + + rnm = &((conf->rnm)[mpl-1]); + + if (conf->trace) + dump_rnm(mpl, rnm, "update"); + + written = snprintf(usermod, sizeof(usermod), + "%s -G %s -c \"%s\" \"%s\"", USERMOD, rnm->groups, user, user); + + if (written >= sizeof(usermod)) { + syslog(LOG_ERR, + "%s: truncated usermod cmd. Skipping:\"%s\"\n", conf->prog, usermod); + return radius_update_user_cleanup(STATUS_E2BIG); + } + + return radius_update_user_cleanup(invoke_popen(conf, usermod)); +} + +static int radius_create_user_cleanup(int status) { + return status; +} + +int radius_create_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl, + int unconfirmed) { + + char buf[BUFLEN]; + char useradd[4096]; + RADIUS_NSS_MPL * rnm = &((conf->rnm)[mpl-1]); + int written = 0; + + if (conf->trace) + dump_rnm(mpl, rnm, "create"); + + + if (conf->many_to_one) { + + written = snprintf(useradd, sizeof(useradd), + "%s -g %d -G %s -c \"%s\" -m -s %s \"%s\"", + USERADD, rnm->gid, rnm->groups, rnm->gecos, rnm->shell, user); + + } else { + + snprintf(buf, sizeof(buf), "Unconfirmed-%ld", time(NULL)); + written = snprintf(useradd, sizeof(useradd), + "%s -U -G %s -c \"%s\" -d \"/home/%s\" -m -s %s \"%s\"", + USERADD, rnm->groups, unconfirmed ? buf : user, user, + rnm->shell, user); + + } + + if (written >= sizeof(useradd)) { + syslog(LOG_ERR, + "%s: truncated useradd cmd. Skipping:\"%s\"\n", conf->prog, useradd); + return radius_create_user_cleanup(STATUS_E2BIG); + } + + syslog(LOG_INFO, "%s: Creating user \"%s\"", conf->prog, user); + + return radius_create_user_cleanup(invoke_popen(conf, useradd)); +} + +static int radius_delete_user_cleanup(int status) { + return status; +} + +int radius_delete_user(RADIUS_NSS_CONF_B * conf, const char * user) { + + char buf[BUFLEN]; + char userdel[4096]; + int written = 0; + + written = snprintf(userdel, sizeof(userdel), "%s -r \"%s\"", USERDEL, user); + + if (written >= sizeof(userdel)) { + syslog(LOG_ERR, + "%s: truncated userdel cmd. Skipping:\"%s\"\n", conf->prog, userdel); + return radius_delete_user_cleanup(STATUS_E2BIG); + } + + syslog(LOG_INFO, "%s: Deleting user \"%s\"", conf->prog, user); + + return radius_delete_user_cleanup(invoke_popen(conf, userdel)); +} + +int radius_clear_unconfirmed_users_cleanup(int status, FILE * fp) { + if (fp) + fclose(fp); + return status; +} + +int radius_clear_unconfirmed_users(RADIUS_NSS_CONF_B * conf) +{ + FILE *fp; + int status; + time_t ts, curr = time(NULL); + struct passwd pw, * pwd = & pw, * result = NULL; + char buf[BUFLEN]; + + if ((fp = fopen(ETC_PASSWD, "r")) == NULL) { + syslog(LOG_ERR, "%s: fopen(\"/etc/passwd\") failed\n", conf->prog); + return radius_clear_unconfirmed_users_cleanup(STATUS_ENOENT, fp); + } + + while((status = fgetpwent_r(fp, pwd, buf, sizeof(buf), &result)) == 0) { + if ( (result) + && (strncmp((result)->pw_gecos, "Unconfirmed-", 12) == 0) + && (ts = atoi(&(((result)->pw_gecos)[12]))) + && ((curr - ts) >= conf->unconfirmed_ageout)) { + + syslog(LOG_INFO, "%s: Deleting unconfirmed user \"%s\"", + conf->prog, (result)->pw_name); + + return radius_clear_unconfirmed_users_cleanup( + radius_delete_user(conf, (result)->pw_name), fp); + } + } + + return radius_clear_unconfirmed_users_cleanup(STATUS_ESRCH, fp); +} + + +int radius_lookup_cache_cleanup( int status, int rafd) { + if (rafd != -1) + close(rafd); + return status; +} + +int radius_lookup_cache( char * prog, const char * nam, int * pmpl) { + int rafd = -1; + int i; + char cache_filename[PATH_MAX]; + char file_buf[10]; + struct stat sb; + int flags; + int written; + int mpl; + + *pmpl = RADIUS_MIN_MPL; + + if ((written = snprintf(cache_filename, sizeof(cache_filename), "%s/%s/%s", + RADIUS_ATTRIBUTE_CACHE_DIR, nam, RADIUS_ATTR_MPL) + >= sizeof(cache_filename))) { + syslog( LOG_ERR, "%s: \"%s\": too long.", prog, cache_filename); + return radius_lookup_cache_cleanup(STATUS_E2BIG, rafd); + } + + /* Ensure the user's cache exists + */ + if (((rafd = open(cache_filename, O_RDONLY)) == -1) + || (fstat(rafd, &sb) == -1) + || (((flags = fcntl(rafd, F_GETFL, 0)) == -1) && ((flags = 0) != 0)) + || (fcntl(rafd, F_SETFL, flags | O_NONBLOCK) == -1)) { + syslog( LOG_INFO, "%s: \"%s\": Absent.", prog, cache_filename); + return radius_lookup_cache_cleanup(STATUS_ENOENT, rafd); + } + + /* Read the privilege attribute file. + */ + + if (sb.st_size >= sizeof(file_buf)) { + syslog( LOG_WARNING, "%s: size %ld greater than %ld. Ignoring", + cache_filename, sb.st_size, sizeof(file_buf)-1); + return radius_lookup_cache_cleanup(STATUS_EFBIG, rafd); + } + + if ((i = read(rafd, file_buf, sizeof(file_buf))) != sb.st_size) { + syslog( LOG_WARNING, "%s: read %d of %ld. Ignoring", prog, + i, sb.st_size); + return radius_lookup_cache_cleanup(STATUS_EIO, rafd); + } + + file_buf[sb.st_size] = 0; + mpl = atoi(file_buf); + + if (((RADIUS_MIN_MPL <= mpl) && (mpl <= RADIUS_MAX_MPL))) + *pmpl = mpl; + + return radius_lookup_cache_cleanup(0, rafd); +} + +int radius_copy_pw( RADIUS_NSS_CONF_B * conf, struct passwd * res, + const char * nam, struct passwd * pwd, + char * buffer, size_t buflen, int * errnop) { + + int ret = 0; + size_t namlen, gecoslen, dirlen, shelllen; + + size_t totallen = (namlen = strlen(res->pw_name)) + 1 + + (gecoslen = strlen(res->pw_gecos)) + 1 + + (dirlen = strlen(res->pw_dir)) + 1 + + (shelllen = strlen(res->pw_shell)) + 1 + + 1 + 1; + + if (totallen > buflen) { + if (errnop) + *errnop = ERANGE; + ret = 1; + if (conf->debug) + syslog(LOG_INFO, "(%s->%s): %s: getpwnam_r ERANGE %ld < %ld\n", + conf->prog, nam, res->pw_name, buflen, totallen); + return ret; + } + + memcpy( buffer, res->pw_name, namlen + 1); + pwd->pw_name = buffer; + buffer += (namlen + 1); + + memcpy( buffer, "*", 1 + 1); + pwd->pw_passwd = buffer; + buffer += (1 + 1); + + pwd->pw_uid = res->pw_uid; + + pwd->pw_gid = res->pw_gid; + + memcpy( buffer, res->pw_gecos, gecoslen + 1); + pwd->pw_gecos = buffer; + buffer += (gecoslen + 1); + + memcpy( buffer, res->pw_dir, dirlen + 1); + pwd->pw_dir = buffer; + buffer += (dirlen + 1); + + memcpy( buffer, res->pw_shell, shelllen + 1); + pwd->pw_shell = buffer; + buffer += (shelllen + 1); + + return ret; +} + + +static int is_sshd_lookup_exit(int status, int fd, regex_t * re) { + if (fd != -1) + close(fd); + + if (re) + regfree(re); + + return status; +} + + +int is_sshd_lookup(RADIUS_NSS_CONF_B * conf, const char * name) { + pid_t pid = getpid(); + int fd, i; + regex_t regex, * re = NULL; + int reg_ret; + + +#define PROC_FILENAME_LEN 128 +#define CMDLINE_SZ 128 + + char proc_file[PROC_FILENAME_LEN]; + char cmdline[CMDLINE_SZ], * colon; + char expected[CMDLINE_SZ]; + char accepted[CMDLINE_SZ]; + char errbuf[CMDLINE_SZ]; + + snprintf(proc_file, sizeof(proc_file), "/proc/%ld/cmdline", (long) pid); + snprintf(expected, sizeof(expected), ": %s [priv]", name); + snprintf(accepted, sizeof(accepted), ": [accepted]"); + + if ((fd = open(proc_file, O_RDONLY)) == -1) { + + syslog( LOG_WARNING, "%s: open(%s) failed: errno %d", + conf->prog, proc_file, errno); + return is_sshd_lookup_exit(0, fd, re); + } + + if ((i = read(fd, cmdline, sizeof(cmdline)-1)) <= 0) { + syslog( LOG_WARNING, "%s: %s: read(%d) failed: errno %d. Ignoring", + conf->prog, proc_file, fd, errno); + return is_sshd_lookup_exit(0, fd, re); + } + + if (conf->unconfirmed_regexp) { + + if ((reg_ret = regcomp(®ex, conf->unconfirmed_regexp, + REG_EXTENDED|REG_NOSUB))) { + + errbuf[0] = 0; + regerror(reg_ret, ®ex, errbuf, sizeof(errbuf)); + syslog( LOG_ERR, "%s: %s: regcomp() failed: %s", conf->prog, + conf->unconfirmed_regexp, errbuf); + + } else { + + re = ®ex; + + } + } + + if (re) { + + if (!(reg_ret = regexec(re, cmdline, 0, NULL, 0))) { + syslog( LOG_INFO, "%s: %s: Lookup %s", conf->prog, cmdline, name); + return is_sshd_lookup_exit(1, fd, re); + } + + } else { + + cmdline[i] = 0; + colon = strchr(cmdline, ':'); + + if (colon && ((0 == strncmp(expected, colon, sizeof(expected) - 1)) || + (0 == strncmp(accepted, colon, sizeof(accepted) - 1)))) { + syslog( LOG_INFO, "%s: %s: Lookup %s", conf->prog, cmdline, name); + return is_sshd_lookup_exit(1, fd, re); + } + } + + if (conf->debug) + syslog( LOG_DEBUG, "%s: %s: Non sshd Lookup %s", + conf->prog, cmdline, name); + + return is_sshd_lookup_exit(0, fd, re); +} + diff --git a/src/radius/nss/libnss-radius/nss_radius_common.h b/src/radius/nss/libnss-radius/nss_radius_common.h new file mode 100644 index 000000000000..238935a6e6be --- /dev/null +++ b/src/radius/nss/libnss-radius/nss_radius_common.h @@ -0,0 +1,132 @@ +/* +Copyright 2020 Broadcom. All rights reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +*/ + +/* + * nss_radius_common.h + * + * Common code for NSS for RADIUS cached MPL(Management Privilege Attribute). + * Used by both NSS module(uses the cache) and PAM module(updates the cache) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RADIUS_MAX_MPL (15) +#define RADIUS_MIN_MPL (1) + +#define RADIUS_NSS_CONF "/etc/radius_nss.conf" +#define RADIUS_MAX_NSS_CONF_SZ 2048 + +#define RADIUS_ATTRIBUTE_CACHE_DIR "/var/cache/radius/user" +#define RADIUS_CACHE_DIR "/var/cache/radius" +#define RADIUS_ATTR_MPL "Management-Privilege-Level" + +#define ETC_PASSWD "/etc/passwd" + +#define USERADD "/usr/sbin/useradd" +#define USERMOD "/usr/sbin/usermod" +#define USERDEL "/usr/sbin/userdel" + +#define BUFLEN 4096 + +#define UNCONFIRMED_AGEOUT_DEFAULT 600 +#define UNCONFIRMED_CLEAR_LIMIT_DEFAULT 10 + +#define RADIUS_CONFIRMED 0 +#define RADIUS_UNCONFIRMED 1 + +#if defined(TEST_RADIUS_NSS) + +#undef RADIUS_NSS_CONF +#define RADIUS_NSS_CONF "radius_nss.conf" + +#undef RADIUS_CACHE_DIR +#define RADIUS_CACHE_DIR "." + +#undef RADIUS_ATTRIBUTE_CACHE_DIR +#define RADIUS_ATTRIBUTE_CACHE_DIR "user" + + +#undef ETC_PASSWD +#define ETC_PASSWD "passwd" + +#undef USERADD +#define USERADD "/bin/echo" +#undef USERMOD +#define USERMOD "/bin/echo" +#undef USERDEL +#define USERDEL "/bin/echo" + +#define syslog(priority,format,...) fprintf(stderr,format"\n",__VA_ARGS__) + +#endif + + +#define STATUS_EPERM 1 +#define STATUS_ENOENT 2 +#define STATUS_ESRCH 3 +#define STATUS_EIO 5 +#define STATUS_E2BIG 7 +#define STATUS_EINVAL 22 +#define STATUS_EFBIG 27 + + + +typedef struct _radius_nss_mpl { + gid_t gid; + char * groups; /* Supplementary groups */ + char * gecos; + char * shell; +} RADIUS_NSS_MPL; + +typedef struct _radius_nss_conf { + char * prog; + int debug; + int trace; + int many_to_one; + int allow_anonymous; + char * unconfirmed_regexp; + int unconfirmed_ageout; + int unconfirmed_clear_limit; + RADIUS_NSS_MPL rnm[RADIUS_MAX_MPL]; +} RADIUS_NSS_CONF_B; + +int parse_nss_config( RADIUS_NSS_CONF_B * conf, char * prog, + char * file_buf, int file_buf_sz, int * errnop, int * plockfd); + +int unparse_nss_config( RADIUS_NSS_CONF_B * conf, int * errnop, int * plockfd); + +int radius_lookup_cache( char * prog, const char * nam, int * pmpl); + +int radius_fill_pw( RADIUS_NSS_CONF_B * conf, int mpl, + const char * nam, struct passwd * pwd, + char * buffer, size_t buflen, int * errnop); + +int radius_copy_pw( RADIUS_NSS_CONF_B * conf, struct passwd * res, + const char * nam, struct passwd * pwd, + char * buffer, size_t buflen, int * errnop); + +int is_sshd_lookup(RADIUS_NSS_CONF_B * conf, const char * nam); + +int radius_getpwnam_r(char * prog, const char * name, + struct passwd * pwd, char * buf, int buflen, struct passwd ** result); +int radius_update_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl); +int radius_create_user(RADIUS_NSS_CONF_B * conf, const char * user, int mpl, + int unconfirmed); +int radius_clear_unconfirmed_users(RADIUS_NSS_CONF_B * conf); + diff --git a/src/radius/nss/libnss-radius/rbash_restriction.sh b/src/radius/nss/libnss-radius/rbash_restriction.sh new file mode 100644 index 000000000000..50158abd4993 --- /dev/null +++ b/src/radius/nss/libnss-radius/rbash_restriction.sh @@ -0,0 +1,19 @@ +####################################################################### +# +# Copyright 2019 Broadcom. All rights reserved. +# The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +# +####################################################################### +# +# Restrict rbash +# + +if [ "$0" == "rbash" ] ; then + + # Prevent from executing arbitrary commands. Only allow CWD commands + # Add shell scripts to the user's home directory to allow them to run + # commands. + + PATH= + export PATH +fi diff --git a/src/radius/nss/libnss-radius/test_nss_radius.c b/src/radius/nss/libnss-radius/test_nss_radius.c new file mode 100644 index 000000000000..edae8891b1cc --- /dev/null +++ b/src/radius/nss/libnss-radius/test_nss_radius.c @@ -0,0 +1,62 @@ +/* +Copyright 2019 Broadcom. All rights reserved. +The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. +*/ + +/* + * Test code for _nss_radius_getpwnam_r(): NSS entry point for getpwnam + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int ac, char * av[]) { + + + enum nss_status status; + struct passwd pw; + char buf[256]; + int errno; + + char * users[] = { "admin", "user", "netops", "operator", "unknown", 0 }; + char ** u; + + printf("buf: %p, len: %lx\n", buf, sizeof(buf)); + + for ( u = users ; *u ; u++) { + status = _nss_radius_getpwnam_r( *u, &pw, buf, sizeof(buf), + &errno); + + printf("\n%s: status:%d\n", *u, status); + + if (status != NSS_STATUS_SUCCESS) + continue; + + printf("\tnam: %s, passwd: %s, uid: %u, gid: %u\n", + pw.pw_name, pw.pw_passwd, pw.pw_uid, pw.pw_gid); + printf("\tnam: %p, passwd: %p, uid: %u, gid: %u\n", + pw.pw_name, pw.pw_passwd, pw.pw_uid, pw.pw_gid); + + printf("\n"); + + printf("\tgecos: %s, dir: %s, shell: %s\n", + pw.pw_gecos, pw.pw_dir, pw.pw_shell); + printf("\tgecos: %p, dir: %p, shell: %p\n", + pw.pw_gecos, pw.pw_dir, pw.pw_shell); + } + + return status; +} + diff --git a/src/radius/nss/patches/.gitignore b/src/radius/nss/patches/.gitignore new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/radius/pam/.gitignore b/src/radius/pam/.gitignore new file mode 100644 index 000000000000..c6ac87447ac0 --- /dev/null +++ b/src/radius/pam/.gitignore @@ -0,0 +1 @@ +pam_radius diff --git a/src/radius/pam/Makefile b/src/radius/pam/Makefile new file mode 100644 index 000000000000..c170f6095f80 --- /dev/null +++ b/src/radius/pam/Makefile @@ -0,0 +1,43 @@ +.ONESHELL: +SHELL = /bin/bash +.SHELLFLAGS += -e + +MAIN_TARGET = libpam-radius-auth_$(PAM_RADIUS_VERSION)_amd64.deb + +$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% : + # Build freeradius-server v1.1.x + pushd freeradius + -rm -rf freeradius-server + git clone https://github.com/FreeRADIUS/freeradius-server.git + popd + pushd freeradius/freeradius-server + git checkout -f 5f715dba4d2dbdb268bf60fcc656352274930941 + # Apply patch + patch -p1 < ../patches/freeradius_configure.patch + patch -p1 < ../patches/freeradius_2007-04-06.patch + patch -p1 < ../patches/freeradius_libeap_deprecated_openssl_1_0.patch + ./configure --disable-static --enable-libtool-lock + make + popd + + # Obtain pam_radius + -rm -rf ./pam_radius + git clone https://github.com/FreeRADIUS/pam_radius.git + pushd ./pam_radius + # This is the latest commit with Radius MPL support + git checkout -f 149c25df84cf5cd0e9addd9346699a9ca8fdddd2 + + cp -r ../debian . + cp -r ../patches . + + # Apply patch + git apply patches/0001-chap-support.patch + git apply patches/0002-peap-mschapv2-support.patch + git apply patches/0003-nas-ip-address-config.patch + + dpkg-buildpackage -rfakeroot -b -us -uc -nc + popd + + mv $(DERIVED_TARGETS) $* $(DEST)/ + +$(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET) diff --git a/src/radius/pam/debian/README.Debian b/src/radius/pam/debian/README.Debian new file mode 100644 index 000000000000..d0bf25b50fcf --- /dev/null +++ b/src/radius/pam/debian/README.Debian @@ -0,0 +1,5 @@ +NOTE: The Debian version of libpam-radius-auth uses as default configuration +file /etc/pam_radius_auth.conf. +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. diff --git a/src/radius/pam/debian/changelog b/src/radius/pam/debian/changelog new file mode 100644 index 000000000000..97e545cb63a3 --- /dev/null +++ b/src/radius/pam/debian/changelog @@ -0,0 +1,5 @@ +libpam-radius-auth (1.4.1-1) unstable; urgency=low + + * Initial version. + + -- Arun Barboza <29963827+a-barboza@users.noreply.github.com> Tue, 13 Aug 2019 16:46:25 -0700 diff --git a/src/radius/pam/debian/compat b/src/radius/pam/debian/compat new file mode 100644 index 000000000000..f599e28b8ab0 --- /dev/null +++ b/src/radius/pam/debian/compat @@ -0,0 +1 @@ +10 diff --git a/src/radius/pam/debian/control b/src/radius/pam/debian/control new file mode 100644 index 000000000000..82642deffbc3 --- /dev/null +++ b/src/radius/pam/debian/control @@ -0,0 +1,16 @@ +Source: libpam-radius-auth +Maintainer: Arun Barboza <29963827+a-barboza@users.noreply.github.com> +Section: libs +Priority: extra +Standards-Version: 3.6.2 +Build-Depends: libpam0g-dev | libpam-dev, debhelper (>= 10~) + +Package: libpam-radius-auth +Architecture: any +Depends: ${shlibs:Depends} +Suggests: radius-server +Description: The PAM RADIUS authentication module + 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, however, need to supply your own RADIUS server to + perform the actual authentication diff --git a/src/radius/pam/debian/copyright b/src/radius/pam/debian/copyright new file mode 100644 index 000000000000..5c31e50ff07c --- /dev/null +++ b/src/radius/pam/debian/copyright @@ -0,0 +1,284 @@ +Please see the individual src files for the copyright + +------------------------------------------------------------------------- + + 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/src/radius/pam/debian/index.html b/src/radius/pam/debian/index.html new file mode 100644 index 000000000000..bbd65616d805 --- /dev/null +++ b/src/radius/pam/debian/index.html @@ -0,0 +1,36 @@ + + +<code>pam_radius_auth</CODE>: The PAM RADIUS authentication module + + +

pam_radius_auth: The PAM RADIUS authentication module

+ +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. + +


+

Files included with the module

+ +README Introduction and documentation
+INSTALL Installation instructions
+USAGE Module configuration and usage documentation
+Changelog What's changed
+pam_radius_auth.conf Sample +configuration file for telling the client the location of the RADIUS server.
+Mini Debian HOWTO C source file
+
+ + +


+

Updates

+ +For the latest version and updates, see the main web or ftp site: +

+http://www.freeradius.org/pam_radius_auth/
+ftp://ftp.freeradius.org/pub/radius/pam_radius_auth.tar +


+$Id: index.html,v 1.4 2001/03/30 19:01:56 aland Exp $ + + diff --git a/src/radius/pam/debian/pam_example b/src/radius/pam/debian/pam_example new file mode 100644 index 000000000000..1ab27e861631 --- /dev/null +++ b/src/radius/pam/debian/pam_example @@ -0,0 +1,64 @@ +This is a simple and safe example on how to enable radius +authentication to the console login on a Debian system and +you are too lazy to read the USAGE documentation. + +Edit /etc/pam.d/login + +The default looks like: + +[SNIP] + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# This module parses /etc/environment (the standard for setting +# environ vars) and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# (Replaces the `ENVIRON_FILE' setting from login.defs) +auth required pam_env.so + +# Standard Un*x authentication. The "nullok" line allows passwordless +# accounts. +@include common-auth + +[SNIP] + + +Insert the following line: + +auth sufficient pam_radius_auth.so + +AFTER + +auth required pam_env.so + +and BEFORE + +# Standard Un*x authentication. The "nullok" line allows passwordless +# accounts. +@include common-auth + +so that it will looks like: + +[SNIP] + +# This module parses /etc/environment (the standard for setting +# environ vars) and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# (Replaces the `ENVIRON_FILE' setting from login.defs) +auth required pam_env.so + +##### RADIUS ##### +auth sufficient pam_radius_auth.so + +# Standard Un*x authentication. The "nullok" line allows passwordless +# accounts. +@include common-auth + +[SNIP] + +Try now to login in one of the consoles using the radius password. +If it fails the system will prompt again for a password. This time +provide the local one. + diff --git a/src/radius/pam/debian/rules b/src/radius/pam/debian/rules new file mode 100755 index 000000000000..5d1f9a3d6c2c --- /dev/null +++ b/src/radius/pam/debian/rules @@ -0,0 +1,116 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 + +# Build options. +CC=gcc +CFLAGS= -g -Wall -fPIC -DCONF_FILE=\"/etc/pam_radius_auth.conf\" +#LDFLAGS= +#CFLAGS= -g -Wall -fPIC +#LDFLAGS = -shared + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +ifeq ($(DEB_HOST_GNU_CPU),(hppa|m68k|mips|powerpc|s390|sparc|sparc64|sheb)) + CFLAGS += -DHIGHFIRST +endif + +export CFLAGS +#export LDFLAGS +export CC + +build: patch build-stamp + +build-stamp: + dh_testdir + + # Add here commands to compile the package. + dh_auto_configure -- --enable-pamdir=/lib/$(DEB_HOST_MULTIARCH)/security + +# $(MAKE) -e + $(MAKE) + + touch build-stamp + +#patch: +# if [ ! -f patch-stamp ]; then \ +# patch -p1 < debian/patches/001.fix_Makefile.diff && \ +# patch -p1 < debian/patches/002.CAN2005-0108.diff && \ +# touch patch-stamp; \ +# fi +# +#unpatch: +# if [ -f patch-stamp ]; then \ +# patch -Rp1 < debian/patches/002.CAN2005-0108.diff && \ +# patch -Rp1 < debian/patches/001.fix_Makefile.diff && \ +# rm -f patch-stamp; \ +# fi + +patch: + if [ ! -f patch-stamp ]; then \ + touch patch-stamp; \ + fi + +unpatch: + if [ -f patch-stamp ]; then \ + rm -f patch-stamp; \ + fi + +clean: unpatch real-clean +real-clean: + dh_testdir + dh_testroot + rm -f build-stamp + + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs /lib /lib/$(DEB_HOST_MULTIARCH) /lib/$(DEB_HOST_MULTIARCH)/security /etc /etc/logrotate.d /etc/pam_radius_auth.d /etc/pam_radius_auth.d/statistics /usr/share/doc/libpam-radius-auth/html + + install -p pam_radius_auth.so debian/libpam-radius-auth/lib/$(DEB_HOST_MULTIARCH)/security/pam_radius_auth.so + install -p ../freeradius/freeradius-server/src/lib/.libs/libradius-1.1.8.so debian/libpam-radius-auth/lib/$(DEB_HOST_MULTIARCH)/libradius-1.1.8.so + install -p ../freeradius/freeradius-server/src/modules/rlm_eap/libeap/.libs/libeap-1.1.8.so debian/libpam-radius-auth/lib/$(DEB_HOST_MULTIARCH)/libeap-1.1.8.so + install -p pam_radius_auth.conf debian/libpam-radius-auth/etc/pam_radius_auth.conf + install -p pam_radius debian/libpam-radius-auth/etc/logrotate.d/pam_radius + install -p index.html debian/libpam-radius-auth/usr/share/doc/libpam-radius-auth/html/index.html + install -p debian/index.html debian/libpam-radius-auth/usr/share/doc/libpam-radius-auth/html/index.debian.html + +# Build architecture-independent +binary-indep: build install + # nothing to do + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs Changelog + dh_installdocs README.rst TODO USAGE debian/README.Debian + dh_installexamples pam_radius_auth.conf debian/pam_example INSTALL + dh_strip + dh_compress usr/share/doc/libpam-radius-auth/README.rst\ + usr/share/doc/libpam-radius-auth/README.Debian\ + usr/share/doc/libpam-radius-auth/USAGE\ + usr/share/doc/libpam-radius-auth/examples/INSTALL + dh_fixperms + chmod 600 debian/libpam-radius-auth/etc/pam_radius_auth.conf + chmod 644 debian/libpam-radius-auth/etc/logrotate.d/pam_radius + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-arch binary-indep +.PHONY: build clean binary-arch binary install patch unpatch diff --git a/src/radius/pam/freeradius/.gitignore b/src/radius/pam/freeradius/.gitignore new file mode 100644 index 000000000000..d10eb9f8b0c7 --- /dev/null +++ b/src/radius/pam/freeradius/.gitignore @@ -0,0 +1,2 @@ +freeradius-1.1.4 +freeradius-server diff --git a/src/radius/pam/freeradius/patches/freeradius_2007-04-06.patch b/src/radius/pam/freeradius/patches/freeradius_2007-04-06.patch new file mode 100644 index 000000000000..7b31965ddd70 --- /dev/null +++ b/src/radius/pam/freeradius/patches/freeradius_2007-04-06.patch @@ -0,0 +1,113 @@ +diff -Naurp freeradius-1.1.4_orig/src/include/radiusd.h freeradius-1.1.4/src/include/radiusd.h +--- freeradius-1.1.4_orig/src/include/radiusd.h 2007-04-06 05:12:44.644347000 -0700 ++++ freeradius-1.1.4/src/include/radiusd.h 2007-04-06 05:15:03.427150000 -0700 +@@ -218,9 +218,9 @@ typedef struct main_config_t { + REALM *realms; + } MAIN_CONFIG_T; + +-#define DEBUG if(debug_flag)log_debug +-#define DEBUG2 if (debug_flag > 1)log_debug +-#define DEBUG3 if (debug_flag > 2)log_debug ++#define DEBUG if(debug_flag) peap_log_debug ++#define DEBUG2 if (debug_flag > 1) peap_log_debug ++#define DEBUG3 if (debug_flag > 2) peap_log_debug + + #define SECONDS_PER_DAY 86400 + #define MAX_REQUEST_TIME 30 +@@ -354,7 +354,7 @@ int radlog(int, const char *, ...) + __attribute__ ((format (printf, 2, 3))) + #endif + ; +-int log_debug(const char *, ...) ++int peap_log_debug(const char *, ...) + #ifdef __GNUC__ + __attribute__ ((format (printf, 1, 2))) + #endif +--- freeradius-1.1.4_orig/Makefile 2006-06-22 21:31:58.000000000 -0700 ++++ freeradius-1.1.4/Makefile 2018-01-16 23:26:04.820218000 -0800 +@@ -40,31 +40,16 @@ clean: + # + export DESTDIR := $(R) + install: +- $(INSTALL) -d -m 755 $(R)$(sbindir) +- $(INSTALL) -d -m 755 $(R)$(bindir) +- $(INSTALL) -d -m 755 $(R)$(raddbdir) +- $(INSTALL) -d -m 755 $(R)$(mandir) +- $(INSTALL) -d -m 755 $(R)$(RUNDIR) +- $(INSTALL) -d -m 700 $(R)$(logdir) +- $(INSTALL) -d -m 700 $(R)$(radacctdir) +- $(INSTALL) -d -m 755 $(R)$(datadir) +- $(INSTALL) -d -m 755 $(R)$(dictdir) +- for i in 1 5 8; do \ +- $(INSTALL) -d -m 755 $(R)$(mandir)/man$$i; \ +- for p in man/man$$i/*.$$i; do \ +- $(INSTALL) -m 644 $$p $(R)$(mandir)/man$$i; \ +- done \ ++ $(INSTALL) -d -m 755 $(PKG_CONFIG_SYSROOT_DIR)$(includedir)/freeradius ++ for p in src/include/*.h \ ++ src/modules/rlm_eap/*.h \ ++ src/modules/rlm_eap/libeap/*.h ; do \ ++ $(INSTALL) -m 644 $$p \ ++ $(PKG_CONFIG_SYSROOT_DIR)$(includedir)/freeradius; \ + done +- @$(MAKE) $(MFLAGS) WHAT_TO_MAKE=$@ common +- @echo "Installing dictionary files in $(R)$(dictdir)"; \ +- cd share; \ +- for i in dictionary*; do \ +- $(INSTALL) -m 644 $$i $(R)$(dictdir); \ +- done +- $(LIBTOOL) --finish $(R)$(libdir) + + common: +- @for dir in $(SUBDIRS); do \ ++ @for dir in src; do \ + echo "Making $(WHAT_TO_MAKE) in $$dir..."; \ + $(MAKE) $(MFLAGS) -C $$dir $(WHAT_TO_MAKE) || exit $$?; \ + done +diff -Naurp freeradius-1.1.4_orig/src/Makefile freeradius-1.1.4/src/Makefile +--- freeradius-1.1.4_orig/src/Makefile 2006-05-25 09:24:40.000000000 -0700 ++++ freeradius-1.1.4/src/Makefile 2018-01-10 13:32:07.572104095 -0800 +@@ -20,7 +20,7 @@ install: + @$(MAKE) $(MFLAGS) WHAT_TO_MAKE=$@ common + + common: +- @for dir in $(SUBDIRS); do \ ++ @for dir in include lib modules; do \ + echo "Making $(WHAT_TO_MAKE) in $$dir..."; \ + $(MAKE) $(MFLAGS) -C $$dir $(WHAT_TO_MAKE) || exit $$?; \ + done +diff -Naurp freeradius-1.1.4_orig/src/modules/Makefile freeradius-1.1.4/src/modules/Makefile +--- freeradius-1.1.4_orig/src/modules/Makefile 2006-07-06 12:16:41.000000000 -0700 ++++ freeradius-1.1.4/src/modules/Makefile 2018-01-10 13:35:28.596154618 -0800 +@@ -43,7 +43,7 @@ reconfig: + + common: + @[ -d lib/ ] || mkdir lib +- @for mod in $(MODULES); do \ ++ @for mod in rlm_eap; do \ + if [ -d $$mod ] && [ -f $$mod/Makefile ]; then \ + echo "Making $(WHAT_TO_MAKE) in $$mod..."; \ + $(MAKE) $(MFLAGS) -C $$mod $(WHAT_TO_MAKE) || exit $$?; \ +diff -Naurp freeradius-1.1.4_orig/src/modules/rlm_eap/Makefile.in freeradius-1.1.4/src/modules/rlm_eap/Makefile.in +--- freeradius-1.1.4_orig/src/modules/rlm_eap/Makefile.in 2006-05-25 09:24:41.000000000 -0700 ++++ freeradius-1.1.4/src/modules/rlm_eap/Makefile.in 2018-01-11 09:46:16.637574778 -0800 +@@ -2,8 +2,8 @@ + # $Id: Makefile.in,v 1.12.2.2.2.4 2006/05/25 16:24:41 nbk Exp $ + # + +-TARGET = @targetname@ +-SRCS = rlm_eap.c eap.c mem.c state.c ++# TARGET = @targetname@ ++# SRCS = rlm_eap.c eap.c mem.c state.c + HEADERS = eap.h rlm_eap.h + RLM_CFLAGS = $(INCLTDL) -Ilibeap + CLIENTLIBS = libeap/libeap.la +@@ -40,7 +40,7 @@ install-subdirs: + $(LIBTOOL) --mode=install $(INSTALL) -m 755 $(INSTALLSTRIP) radeapclient$(EXEEXT) $(R)$(bindir) + + common: +- @for dir in $(RLM_SUBDIRS); do \ ++ @for dir in libeap; do \ + echo "Making $(WHAT_TO_MAKE) in $$dir..."; \ + $(MAKE) $(MFLAGS) -C $$dir $(WHAT_TO_MAKE) || exit $$?; \ + done diff --git a/src/radius/pam/freeradius/patches/freeradius_configure.patch b/src/radius/pam/freeradius/patches/freeradius_configure.patch new file mode 100644 index 000000000000..26e858a6f014 --- /dev/null +++ b/src/radius/pam/freeradius/patches/freeradius_configure.patch @@ -0,0 +1,1443 @@ +diff -Naurp freeradius-server_orig/configure freeradius-server/configure +--- freeradius-server_orig/configure 2021-03-26 12:05:48.205010000 -0700 ++++ freeradius-server/configure 2021-03-26 13:33:49.520827000 -0700 +@@ -23173,217 +23173,6 @@ fi + rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + + if test "x$ucdsnmp" = "x"; then +- old_CFLAGS="$CFLAGS" +- for try in /usr/include /usr/local/include $with_snmp_include_dir; do +- CFLAGS="$old_CFLAGS -I$try" +- cat >conftest.$ac_ext <<_ACEOF +-/* confdefs.h. */ +-_ACEOF +-cat confdefs.h >>conftest.$ac_ext +-cat >>conftest.$ac_ext <<_ACEOF +-/* end confdefs.h. */ +- +-#ifdef HAVE_SYS_TYPES_H +-#include +-#endif +-#ifdef HAVE_STDINT_H +-#include +-#endif +-#ifdef HAVE_STDIO_H +-#include +-#endif +-#ifdef HAVE_NETDB_H +-#include +-#endif +-#ifdef HAVE_UNISTD_H +-#include +-#endif +-#include +-#include +-#include +-#include +-int +-main () +-{ +- int a = 1; +- ; +- return 0; +-} +-_ACEOF +-rm -f conftest.$ac_objext +-if { (ac_try="$ac_compile" +-case "(($ac_try" in +- *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; +- *) ac_try_echo=$ac_try;; +-esac +-eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 +- (eval "$ac_compile") 2>conftest.er1 +- ac_status=$? +- grep -v '^ *+' conftest.er1 >conftest.err +- rm -f conftest.er1 +- cat conftest.err >&5 +- echo "$as_me:$LINENO: \$? = $ac_status" >&5 +- (exit $ac_status); } && { +- test -z "$ac_c_werror_flag" || +- test ! -s conftest.err +- } && test -s conftest.$ac_objext; then +- SNMP_INCLUDE="-I$try";ucdsnmp=yes +-else +- echo "$as_me: failed program was:" >&5 +-sed 's/^/| /' conftest.$ac_ext >&5 +- +- ucdsnmp= +-fi +- +-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +- if test "x$ucdsnmp" != "x"; then +- break; +- fi +- done +- CFLAGS="$old_CFLAGS" +-fi +- +-if test "x$ucdsnmp" = "x"; then +- old_CFLAGS="$CFLAGS" +- for try in /usr/include/ucd-snmp /usr/local/include/ucd-snmp $with_snmp_include_dir; do +- CFLAGS="$old_CFLAGS -I$try" +-cat >conftest.$ac_ext <<_ACEOF +-/* confdefs.h. */ +-_ACEOF +-cat confdefs.h >>conftest.$ac_ext +-cat >>conftest.$ac_ext <<_ACEOF +-/* end confdefs.h. */ +- +-#ifdef HAVE_SYS_TYPES_H +-#include +-#endif +-#ifdef HAVE_STDINT_H +-#include +-#endif +-#ifdef HAVE_STDIO_H +-#include +-#endif +-#ifdef HAVE_NETDB_H +-#include +-#endif +-#ifdef HAVE_UNISTD_H +-#include +-#endif +-#include +-#include +-#include +-int +-main () +-{ +- int a = 1; +- ; +- return 0; +-} +-_ACEOF +-rm -f conftest.$ac_objext +-if { (ac_try="$ac_compile" +-case "(($ac_try" in +- *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; +- *) ac_try_echo=$ac_try;; +-esac +-eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 +- (eval "$ac_compile") 2>conftest.er1 +- ac_status=$? +- grep -v '^ *+' conftest.er1 >conftest.err +- rm -f conftest.er1 +- cat conftest.err >&5 +- echo "$as_me:$LINENO: \$? = $ac_status" >&5 +- (exit $ac_status); } && { +- test -z "$ac_c_werror_flag" || +- test ! -s conftest.err +- } && test -s conftest.$ac_objext; then +- SNMP_INCLUDE="";ucdsnmp=no +-else +- echo "$as_me: failed program was:" >&5 +-sed 's/^/| /' conftest.$ac_ext >&5 +- +- ucdsnmp= +-fi +- +-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +- if test "x$ucdsnmp" != "x"; then +- break; +- fi +- done +- CFLAGS="$old_CFLAGS" +-fi +- +-if test "x$ucdsnmp" = "x"; then +- old_CFLAGS="$CFLAGS" +- for try in /usr/include/ucd-snmp /usr/local/include/ucd-snmp $with_snmp_include_dir; do +- CFLAGS="$old_CFLAGS -I$try" +- cat >conftest.$ac_ext <<_ACEOF +-/* confdefs.h. */ +-_ACEOF +-cat confdefs.h >>conftest.$ac_ext +-cat >>conftest.$ac_ext <<_ACEOF +-/* end confdefs.h. */ +- +-#ifdef HAVE_SYS_TYPES_H +-#include +-#endif +-#ifdef HAVE_STDINT_H +-#include +-#endif +-#ifdef HAVE_STDIO_H +-#include +-#endif +-#ifdef HAVE_NETDB_H +-#include +-#endif +-#ifdef HAVE_UNISTD_H +-#include +-#endif +-#include +-#include +-#include +-int +-main () +-{ +- int a = 1; +- ; +- return 0; +-} +-_ACEOF +-rm -f conftest.$ac_objext +-if { (ac_try="$ac_compile" +-case "(($ac_try" in +- *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; +- *) ac_try_echo=$ac_try;; +-esac +-eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 +- (eval "$ac_compile") 2>conftest.er1 +- ac_status=$? +- grep -v '^ *+' conftest.er1 >conftest.err +- rm -f conftest.er1 +- cat conftest.err >&5 +- echo "$as_me:$LINENO: \$? = $ac_status" >&5 +- (exit $ac_status); } && { +- test -z "$ac_c_werror_flag" || +- test ! -s conftest.err +- } && test -s conftest.$ac_objext; then +- SNMP_INCLUDE="-I$try";ucdsnmp=no +-else +- echo "$as_me: failed program was:" >&5 +-sed 's/^/| /' conftest.$ac_ext >&5 +- +- ucdsnmp= +-fi +- +-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +- if test "x$ucdsnmp" != "x"; then +- break; +- fi +- done +- CFLAGS="$old_CFLAGS" +-fi +- +-if test "x$ucdsnmp" = "x"; then + { echo "$as_me:$LINENO: result: no" >&5 + echo "${ECHO_T}no" >&6; } + else +diff -Naurp freeradius-server_orig/configure.in freeradius-server/configure.in +--- freeradius-server_orig/configure.in 2021-03-26 12:05:48.214820000 -0700 ++++ freeradius-server/configure.in 1969-12-31 16:00:00.000000000 -0800 +@@ -1,1165 +0,0 @@ +-dnl ############################################################# +-dnl # +-dnl # For information about autoconf, see: +-dnl # +-dnl # http://www.gnu.org/software/autoconf/ +-dnl # +-dnl # The recommended order is: +-dnl # +-dnl # AC_INIT(file) +-dnl # 0. checks for compiler, libtool, and command line options +-dnl # 1. checks for programs +-dnl # 2. checks for libraries +-dnl # 3. checks for header files +-dnl # 4. checks for typedefs +-dnl # 5. checks for structures and functions +-dnl # 6. checks for compiler characteristics +-dnl # 7. checks for library functions +-dnl # 8. checks for system services +-dnl # AC_OUTPUT([file...]) +-dnl # +-dnl ############################################################# +- +-AC_INIT(src/main/radiusd.c) +-AC_CONFIG_HEADER(src/include/autoconf.h) +-AC_REVISION($Revision$)dnl +-AC_PREREQ([2.50]) +- +-dnl # The version of the software +-RADIUSD_MAJOR_VERSION=1 +-RADIUSD_MINOR_VERSION=1.8 +-RADIUSD_VERSION="${RADIUSD_MAJOR_VERSION}.${RADIUSD_MINOR_VERSION}" +- +-dnl ############################################################# +-dnl # +-dnl # 0. Checks for compiler, libtool, and command line options. +-dnl # +-dnl ############################################################# +- +-dnl Check for GNU cc +-AC_PROG_CC +- +-dnl # +-dnl # check for AIX, to allow us to use some BSD functions +-dnl # must be before macros that call the compiler. +-dnl # +-AC_AIX +- +-AC_PROG_GCC_TRADITIONAL +-AC_PROG_CC_SUNPRO +-AC_PROG_RANLIB +- +-abs_top_builddir=`pwd` +-AC_SUBST(abs_top_builddir) +- +-dnl # autoconf 2.50 and later +-dnl # AC_SYS_LARGEFILE +- +-PACKAGE=freeradius +- +-dnl # check for system bytesex +-dnl # AC_DEFINES WORDS_BIGENDIAN +-AC_C_BIGENDIAN +- +-dnl Find GNU Make. +-AC_CHECK_PROG(GMAKE, gmake, yes, no) +-if test $GMAKE = no; then +- AC_PATH_PROG(MAKE, make, /usr/local/bin/make) +-else +- AC_PATH_PROG(MAKE, gmake, /usr/local/gnu/bin/make) +-fi +-makever=`$ac_cv_path_MAKE --version 2>&1 | grep "GNU Make"` +-if test -z "$makever"; then +- AC_MSG_ERROR(GNU Make is not installed. Please download and install it +- from ftp://prep.ai.mit.edu/pub/gnu/make/ before continuing.) +-fi +- +-dnl libltdl is installable +-AC_LIBLTDL_INSTALLABLE +- +-dnl tell Makefile to build ltdl if needed +-if test x"$enable_ltdl_install" = x"yes"; then +- LTDL_SUBDIRS=libltdl +-fi +-AC_SUBST(LTDL_SUBDIRS) +- +-dnl use system-wide libtool, if it exists +-AC_ARG_WITH(system-libtool, +-[ --with-system-libtool try to use libtool installed in your system [default=use our own]], +-[ AC_PATH_PROG(LIBTOOL, libtool,,$PATH:/usr/local/bin) ], +-[ +- LIBTOOL="`pwd`/libtool" +- AC_SUBST(LIBTOOL) +- dnl ensure that we're looking for dlopen +- AC_LIBTOOL_DLOPEN +- +- dnl Figure out how to build shared libraries +- AC_PROG_LIBTOOL +-]) +- +- +-dnl Put this in later, when all distributed modules use autoconf. +-dnl AC_ARG_WITH(disablemodulefoo, +-dnl [ --without-rlm_foo Disables module compilation. Module list:] +-dnl esyscmd([find src/modules -type d -name rlm_\* -print |\ +-dnl sed -e 's%src/modules/.*/% (sub)- %; s%.*/%- %' |\ +-dnl awk '{print " "$0}'])) +- +-AC_ARG_ENABLE(strict-dependencies, +-[ --enable-strict-dependencies Fail configure on lack of module dependancy.]) +- +-dnl extra argument: --with-docdir +-docdir='${datadir}/doc/freeradius' +-AC_MSG_CHECKING(docdir) +-AC_ARG_WITH(docdir, +-[ --with-docdir=DIR Directory for documentation [DATADIR/doc/freeradius] ], +-[ case "$withval" in +- no) +- docdir=no +- ;; +- yes) +- ;; +- [[\\/$]]* | ?:[[\\/]]* ) +- docdir="$withval" +- ;; +- *) +- AC_MSG_ERROR([expected an absolute directory name for --with-docdir: $withval]) +- ;; +- esac ] +-) +-AC_SUBST(docdir) +-AC_MSG_RESULT($docdir) +-if test "x$docdir" = xno; then +- AC_MSG_WARN(Documentation files will NOT be installed.) +-fi +- +-dnl extra argument: --with-logdir +-logdir='${localstatedir}/log/radius' +-AC_MSG_CHECKING(logdir) +-AC_ARG_WITH(logdir, +-[ --with-logdir=DIR Directory for logfiles [LOCALSTATEDIR/log/radius] ], +-[ case "$withval" in +- no) +- AC_MSG_ERROR(Need logdir) +- ;; +- yes) +- ;; +- [[\\/$]]* | ?:[[\\/]]* ) +- logdir="$withval" +- ;; +- *) +- AC_MSG_ERROR([expected an absolute directory name for --with-logdir: $withval]) +- ;; +- esac ] +-) +-AC_SUBST(logdir) +-AC_MSG_RESULT($logdir) +- +-dnl extra argument: --with-radacctdir +-radacctdir='${logdir}/radacct' +-AC_MSG_CHECKING(radacctdir) +-AC_ARG_WITH(radacctdir, +-[ --with-radacctdir=DIR Directory for detail files [LOGDIR/radacct] ], +-[ case "$withval" in +- no) +- AC_MSG_ERROR(Need radacctdir) +- ;; +- yes) +- ;; +- [[\\/$]]* | ?:[[\\/]]* ) +- radacctdir="$withval" +- ;; +- *) +- AC_MSG_ERROR([expected an absolute directory name for --with-radacctdir: $withval]) +- ;; +- esac ] +-) +-AC_SUBST(radacctdir) +-AC_MSG_RESULT($radacctdir) +- +-dnl extra argument: --with-raddbdir +-raddbdir='${sysconfdir}/raddb' +-AC_MSG_CHECKING(raddbdir) +-AC_ARG_WITH(raddbdir, +-[ --with-raddbdir=DIR Directory for config files [SYSCONFDIR/raddb] ], +-[ case "$withval" in +- no) +- AC_MSG_ERROR(Need raddbdir) +- ;; +- yes) +- ;; +- [[\\/$]]* | ?:[[\\/]]* ) +- raddbdir="$withval" +- ;; +- *) +- AC_MSG_ERROR([expected an absolute directory name for --with-raddbdir: $withval]) +- ;; +- esac ] +-) +-AC_SUBST(raddbdir) +-AC_MSG_RESULT($raddbdir) +- +-dnl extra argument: --with-ascend-binary +-ASCEND_BINARY=yes +-AC_ARG_WITH(ascend-binary, +-[ --with-ascend-binary Include support for Ascend binary filter attributes (default=yes)], +-[ case "$withval" in +- yes) +- ;; +- *) +- ASCEND_BINARY="" +- esac ] +-) +-if test "X$ASCEND_BINARY" = "Xyes"; then +- AC_DEFINE(ASCEND_BINARY) +-fi +- +-dnl extra argument: --with-threads +-WITH_THREADS=yes +-AC_ARG_WITH(threads, +-[ --with-threads Use threads, if available. (default=yes) ], +-[ case "$withval" in +- yes) +- ;; +- *) +- WITH_THREADS="" +- esac ] +-) +- +-dnl extra argument: --with-snmp +-WITH_SNMP=yes +-AC_ARG_WITH(snmp, +-[ --with-snmp Compile in SNMP support. (default=yes)], +-[ case "$withval" in +- yes) +- ;; +- *) +- WITH_SNMP=no +- esac ] +-) +- +-dnl # +-dnl # extra argument: --with-snmp-include-dir +-dnl # +-AC_ARG_WITH(snmp-include-dir, +-[ --with-snmp-include-dir=DIR Directory to look for include files used by SNMP], +-[ case "$withval" in +- *) +- ;; +- esac ] +-) +- +-dnl extra argument: --with-large-files +-rad_enable_largefiles=no +-AC_ARG_WITH(large-files, +-[ --with-large-files Compile in large (2G+) file support. (default=no)], +-[ case "$withval" in +- yes) +- rad_enable_largefiles=yes +- ;; +- *) +- ;; +- esac ] +-) +- +- +-dnl # +-dnl # Allow the user to specify a list of modules to be linked +-dnl # statically to the server. +-dnl # +-STATIC_MODULES= +-AC_ARG_WITH(static_modules, +-[ --with-static-modules=QUOTED-MODULE-LIST],[ +- for i in $withval; do +- STATIC_MODULES="$STATIC_MODULES -dlpreopen ../modules/rlm_$i/rlm_$i.la" +- done +-]) +- +-MODULES= +-AC_ARG_WITH(modules, +-[ --with-modules=QUOTED-MODULE-LIST],[ +- for i in $withval; do +- MODULES="$MODULES $i" +- done +-]) +- +-dnl # +-dnl # Enable developer C compiler warnings +-dnl # +-AC_ARG_ENABLE(developer, +-[ --enable-developer Enables features of interest to developers.], +-[ case "$enableval" in +- no) +- developer=no +- ;; +- *) +- developer=yes +- esac ] +-) +- +-if test "x$developer" != "xno" -a -d $srcdir/CVS; then +- dnl turn on the developer flag when taken from a CVS checkout (not a release) +- developer="yes" +-fi +- +-dnl extra argument: --with-experimental-modules +-EXPERIMENTAL= +-AC_ARG_WITH(experimental-modules, +-[ --with-experimental-modules Use experimental and unstable modules. (default=no) ], +-[ case "$withval" in +- yes) +- EXPERIMENTAL=yes +- ;; +- *) +- esac ] +-) +- +-dnl extra argument: --with-openssl +-WITH_OPENSSL=yes +-AC_ARG_WITH(openssl, +-[ --with-openssl Use OpenSSL. (default=yes)], +-[ case "$withval" in +- no) +- WITH_OPENSSL=no +- ;; +- *) +- WITH_OPENSSL=yes +- ;; +- esac ] +-) +- +-dnl # +-dnl # extra argument: --with-openssl-includes=dir +-dnl # +-OPENSSL_INCLUDE_DIR= +-AC_ARG_WITH(openssl-includes, +-[ --with-openssl-includes=DIR Directory to look for OpenSSL include files], +-[ case "$withval" in +- *) OPENSSL_INCLUDE_DIR="$withval" +- ;; +- esac ] +-) +- +-dnl # +-dnl # extra argument: --with-openssl-libraries=dir +-dnl # +-OPENSSL_LIB_DIR= +-AC_ARG_WITH(openssl-libraries, +-[ --with-openssl-libraries=DIR Directory to look for OpenSSL library files], +-[ case "$withval" in +- *) OPENSSL_LIB_DIR="$withval" +- ;; +- esac ] +-) +- +-dnl # +-dnl # These next two arguments don't actually do anything. They're +-dnl # place holders so that the top-level configure script can tell +-dnl # the user how to configure lower-level modules +-dnl # +- +-dnl # +-dnl # extra argument: --with-rlm-FOO-lib-dir +-dnl # +-AC_ARG_WITH(rlm-FOO-lib-dir, +-[ --with-rlm-FOO-lib-dir=DIR Directory to look for library files used by module FOO], +-[ case "$withval" in +- *) +- ;; +- esac ] +-) +- +-dnl # +-dnl # extra argument: --with-rlm-FOO-include-dir +-dnl # +-AC_ARG_WITH(rlm-FOO-include-dir, +-[ --with-rlm-FOO-include-dir=DIR Directory to look for include files used by module FOO], +-[ case "$withval" in +- *) +- ;; +- esac ] +-) +- +-dnl See what include-style is used by the make program. +-dnl AC_MSG_CHECKING(include style for make) +-dnl echo "include /dev/null" > testmake.$$ +-dnl echo "all:" >> testmake.$$ +-dnl make -f testmake.$$ >/dev/null 2>&1 +-dnl if test $? = 0 +-dnl then +-dnl INCLUDE=include +-dnl IQUOTE= +-dnl else +-dnl INCLUDE=.include +-dnl IQUOTE='"' +-dnl fi +-dnl rm -f testmake.$$ +-dnl AC_MSG_RESULT(" $INCLUDE") +-dnl AC_SUBST(INCLUDE) +-dnl AC_SUBST(IQUOTE) +- +-dnl extra argument: --with-udpfromto +-WITH_UDPFROMTO=no +-AC_ARG_WITH(udpfromto, +-[ --with-udpfromto Compile in UDPFROMTO support. (default=no)], +-[ case "$withval" in +- yes) +- WITH_UDPFROMTO=yes +- ;; +- *) +- WITH_UDPFROMTO=no +- esac ] +-) +- +-if test "x$WITH_UDPFROMTO" = "xyes"; then +- AC_DEFINE(WITH_UDPFROMTO) +-fi +- +-dnl extra argument: --with-edir +-dnl If using Novell eDirectory, enable UP and Novell specific code +-AC_ARG_WITH(edir, +-[ --with-edir Enable Novell eDirectory integration. (default=no) ] , [] ) +- +-dnl ############################################################# +-dnl # +-dnl # 1. Checks for programs +-dnl # +-dnl ############################################################# +- +-CHECKRAD=checkrad.pl +-AC_PATH_PROG(PERL, perl, /usr/local/bin/perl) +-if test "x$ac_cv_path_PERL" = "x"; then +- AC_MSG_WARN(perl not found - Simultaneous-Use and checkrad.pl may not work) +-fi +-AC_PATH_PROG(SNMPGET, snmpget) +-if test "x$ac_cv_path_SNMPGET" = "x"; then +- AC_MSG_WARN(snmpget not found - Simultaneous-Use and checkrad.pl may not work) +-fi +- +-AC_PATH_PROG(SNMPWALK, snmpwalk) +-if test "x$ac_cv_path_SNMPWALK" = "x"; then +- AC_MSG_WARN(snmpwalk not found - Simultaneous-Use and checkrad.pl may not work) +-fi +- +-AC_PATH_PROG(RUSERS, rusers, /usr/bin/rusers) +- +-dnl FIXME This is truly gross. +-missing_dir=`cd $ac_aux_dir && pwd` +-AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir) +-AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir) +-AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir) +- +-AC_PATH_PROG(LOCATE,locate) +-AC_PATH_PROG(DIRNAME,dirname) +-AC_PATH_PROG(GREP,grep) +- +-dnl ############################################################# +-dnl # +-dnl # 2. Checks for libraries +-dnl # +-dnl ############################################################# +- +-dnl If using pthreads, check for -lpthread (posix) or -lc_r (*BSD) +-old_CFLAGS=$CFLAGS +-if test "x$WITH_THREADS" = "xyes"; then +- if test $ac_cv_prog_suncc = "yes"; then +- CFLAGS="$CFLAGS -mt" +- fi +- +- AC_CHECK_HEADERS(pthread.h, [], [ WITH_THREADS="no" ]) +- +-dnl # +-dnl # pthread stuff is usually in -lpthread +-dnl # or in -lc_r, on *BSD +-dnl # +-dnl # On Some systems, we need extra pre-processor flags, to get them to +-dnl # to do the threading properly. +-dnl # +- AC_CHECK_LIB(pthread, pthread_create, +- [ CFLAGS="$CFLAGS -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS" +- LIBS="$LIBS -lpthread" ], +- AC_CHECK_LIB(c_r, pthread_create, +- [ CFLAGS="$CFLAGS -pthread -D_THREAD_SAFE" ], +- [ WITH_THREADS="no" ] +- ) +- ) +-fi +- +-dnl # +-dnl # If we have NO pthread libraries, remove any knowledge of threads. +-dnl # +-if test "x$WITH_THREADS" != "xyes"; then +- CFLAGS=$old_CFLAGS +- ac_cv_header_pthread_h="no" +- WITH_THREADS=no +-else +- dnl # +- dnl # We need sem_init() and friends, as they're the friendliest +- dnl # semaphore functions for threading. +- dnl # +- dnl # HP/UX requires linking with librt, too, to get the sem_* symbols. +- dnl # Some systems have them in -lsem +- dnl # Solaris has them in -lposix4 +- +- AC_SEARCH_LIBS(sem_init, pthread sem posix4 rt, +- [], +- [AC_MSG_ERROR(-lsem not found. You may want to download it from ftp://ftp.to.gd-es.com/pub/BSDI/libsem.tar.bz2 or ftp://ftp.freeradius.org/pub/radius/contrib/libsem.tar.gz)] +- ) +-fi +- +-dnl Check if we need -lsocket +-AC_CHECK_LIB(socket, getsockname) +- +-dnl Check for -lresolv +-dnl This library may be needed later. +-AC_CHECK_LIB(resolv, inet_aton) +- +-dnl Check if we need -lnsl. Usually if we want to +-dnl link against -lsocket we need to include -lnsl as well. +-AC_CHECK_LIB(nsl, inet_ntoa) +- +-dnl Check for OpenSSL libraries. +-OPENSSL_LIBS= +-if test "x$WITH_OPENSSL" = xyes; then +- old_LIBS=$LIBS +- if test "x$OPENSSL_LIB_DIR" != "x"; then +- LIBS="-L$OPENSSL_LIB_DIR $LIBS" +- fi +- AC_CHECK_LIB(crypto, DH_new, +- [ +- AC_DEFINE(HAVE_LIBCRYPTO, 1, +- [Define to 1 if you have the `crypto' library (-lcrypto).]) +- AC_CHECK_LIB(ssl, SSL_new, +- [ +- AC_DEFINE(HAVE_LIBSSL, 1, +- [Define to 1 if you have the `ssl' library (-lssl).]) +- if test "x$OPENSSL_LIB_DIR" != "x"; then +- OPENSSL_LIBS="-L$OPENSSL_LIB_DIR" +- fi +- OPENSSL_LIBS="$OPENSSL_LIBS -lcrypto -lssl -lcrypto" +- ], []) +- ], []) +- LIBS=$old_LIBS +-fi +- +-dnl ############################################################# +-dnl # +-dnl # 3. Checks for header files +-dnl # +-dnl ############################################################# +- +-dnl # +-dnl # Interix requires us to set -D_ALL_SOURCE, otherwise +-dnl # getopt will be #included, but won't link. +-dnl # +-dnl # +-case "$host" in +-*-interix*) +- CFLAGS="$CFLAGS -D_ALL_SOURCE" +- ;; +-*-darwin*) +- CFLAGS="$CFLAGS -DDARWIN" +- ;; +-esac +- +-AC_HEADER_DIRENT +-AC_HEADER_STDC +-AC_HEADER_TIME +-AC_HEADER_SYS_WAIT +- +-AC_CHECK_HEADERS( \ +- unistd.h \ +- crypt.h \ +- errno.h \ +- resource.h \ +- getopt.h \ +- malloc.h \ +- utmp.h \ +- utmpx.h \ +- signal.h \ +- sys/select.h \ +- syslog.h \ +- inttypes.h \ +- stdint.h \ +- stdio.h \ +- netdb.h \ +- semaphore.h \ +- arpa/inet.h \ +- netinet/in.h \ +- sys/types.h \ +- sys/socket.h \ +- sys/time.h \ +- sys/wait.h \ +- sys/security.h \ +- fcntl.h \ +- sys/fcntl.h \ +- sys/stat.h \ +- sys/prctl.h \ +- prot.h \ +- sia.h \ +- siad.h +-) +- +-REGEX=no +-AC_CHECK_HEADER(regex.h, AC_DEFINE(HAVE_REGEX_H)) +-if test "x$ac_cv_header_regex_h" = "xyes"; then +- REGEX_EXTENDED=no +- REGEX=yes +- AC_EGREP_CPP(yes, +- [#include +- #ifdef REG_EXTENDED +- yes +- #endif +- ], [AC_DEFINE(HAVE_REG_EXTENDED) REGEX_EXTENDED=yes]) +-fi +-AC_SUBST(REGEX) +-AC_SUBST(REGEX_EXTENDED) +- +-dnl # +-dnl # other checks which require headers +-dnl # +-if test "x$ac_cv_header_sys_security_h" = "xyes" && test "x$ac_cv_header_prot_h" = "xyes" +-then +- AC_DEFINE(OSFC2) +-fi +- +-if test "x$ac_cv_header_sia_h" = "xyes" && test "x$ac_cv_header_siad_h" = "xyes" +-then +- AC_DEFINE(OSFSIA) +-fi +- +-dnl Check for OpenSSL includes. +-OPENSSL_INCLUDE="-DNO_OPENSSL" +-if test "x$WITH_OPENSSL" = xyes; then +- if test "x$OPENSSL_LIBS" = "x"; then +- AC_MSG_NOTICE([skipping test for openssl/ssl.h]) +- else +- old_CPPFLAGS=$CPPFLAGS +- if test "x$OPENSSL_INCLUDE_DIR" != "x"; then +- CPPFLAGS="$CPPFLAGS -I$OPENSSL_INCLUDE_DIR" +- fi +- dnl # stupid RedHat shit +- CPPFLAGS="$CPPFLAGS -DOPENSSL_NO_KRB5" +- AC_CHECK_HEADERS( \ +- openssl/ssl.h \ +- openssl/crypto.h \ +- openssl/err.h \ +- openssl/engine.h, +- [], +- OPENSSL_LIBS= +- ) +- if test "x$OPENSSL_LIBS" != "x"; then +- AC_MSG_CHECKING([for OpenSSL version >= 0.9.7]) +- AC_EGREP_CPP(yes, +- [#include +- #if (OPENSSL_VERSION_NUMBER >= 0x00907000L) +- yes +- #endif +- ], goodssl="yes") +- if test "x$goodssl" != "xyes"; then +- AC_MSG_RESULT(no) +- OPENSSL_LIBS= +- else +- AC_MSG_RESULT(yes) +- if test "x$OPENSSL_INCLUDE_DIR" != "x"; then +- OPENSSL_INCLUDE="-I$OPENSSL_INCLUDE_DIR -DOPENSSL_NO_KRB5" +- else +- OPENSSL_INCLUDE="-DOPENSSL_NO_KRB5" +- fi +- fi +- fi +- CPPFLAGS=$old_CPPFLAGS +- fi +-fi +-AC_SUBST(OPENSSL_INCLUDE) +-AC_SUBST(OPENSSL_LIBS) +-export OPENSSL_LIBS +- +-dnl ############################################################# +-dnl # +-dnl # 4. Checks for typedefs +-dnl # +-dnl ############################################################# +- +-dnl # +-dnl # Ensure that these are defined +-dnl # +-AC_TYPE_OFF_T +-AC_TYPE_PID_T +-AC_TYPE_SIZE_T +-AC_TYPE_UID_T +- +-dnl check for socklen_t +-AC_CHECK_TYPE_INCLUDE([ +-#ifdef HAVE_SYS_TYPES_H +-#include +-#endif +-#ifdef HAVE_SYS_SOCKET_H +-#include +-#endif +-],socklen_t, int) +- +-dnl check for uint8_t +-AC_CHECK_TYPE_INCLUDE([ +-#ifdef HAVE_INTTYPES_H +-#include +-#endif +-#ifdef HAVE_STDINT_H +-#include +-#endif +-],uint8_t, unsigned char) +- +-dnl check for uint16_t +-AC_CHECK_TYPE_INCLUDE([ +-#ifdef HAVE_INTTYPES_H +-#include +-#endif +-#ifdef HAVE_STDINT_H +-#include +-#endif +-],uint16_t, unsigned short) +- +-dnl check for uint32_t +-AC_CHECK_TYPE_INCLUDE([ +-#ifdef HAVE_INTTYPES_H +-#include +-#endif +-#ifdef HAVE_STDINT_H +-#include +-#endif +-],uint32_t, unsigned int) +- +-dnl ############################################################# +-dnl # +-dnl # 5. Checks for structures and functions +-dnl # +-dnl ############################################################# +-AC_CHECK_FUNCS( \ +- getopt_long \ +- lockf \ +- strsignal \ +- sigaction \ +- sigprocmask \ +- pthread_sigmask \ +- snprintf \ +- vsnprintf \ +- setsid \ +- strncasecmp \ +- strcasecmp \ +- localtime_r \ +- ctime_r \ +- gmtime_r \ +- strsep \ +- inet_aton \ +- inet_pton \ +- inet_ntop \ +- gethostname \ +- setlinebuf \ +- setvbuf \ +- getusershell \ +- initgroups \ +- closefrom +-) +-RADIUSD_NEED_DECLARATIONS( \ +- crypt \ +- strncasecmp \ +- strcasecmp \ +- inet_aton \ +- gethostname \ +- setlinebuf \ +- getusershell \ +- endusershell +-) +- +-AC_TYPE_SIGNAL +- +-dnl # check if we have utmpx.h +-dnl # if so, check if struct utmpx has entry ut_xtime +-dnl # if not, set it to define ut_xtime == ut_tv.tv_sec +-if test "x$ac_cv_header_utmpx_h" = "xyes" +-then +- AC_CHECK_STRUCT_HAS_MEMBER([#include ], [struct utmpx], ut_xtime) +- if test "x$ac_cv_type_struct_utmpx_has_ut_xtime" = "x" +- then +- AC_DEFINE(ut_xtime,ut_tv.tv_sec) +- fi +-fi +- +-dnl # struct ip_pktinfo +-AC_CHECK_STRUCT_HAS_MEMBER([#include ], [struct in_pktinfo], ipi_addr) +-if test "x$ac_cv_type_struct_in_pktinfo_has_ipi_addr" = "xyes" +-then +- AC_DEFINE(HAVE_IP_PKTINFO) +-fi +- +-dnl ############################################################# +-dnl # +-dnl # 6. Checks for compiler characteristics +-dnl # +-dnl ############################################################# +- +-dnl # +-dnl # Ensure that these are defined +-dnl # +-AC_C_CONST +- +-dnl # +-dnl # See if this is OS/2 +-dnl # +-AC_MSG_CHECKING(type of OS) +-OS=`uname -s` +-AC_MSG_RESULT($OS) +-if test "$OS" = "OS/2"; then +- LIBPREFIX= +-else +- LIBPREFIX=lib +-fi +-AC_SUBST(LIBPREFIX) +- +-dnl # +-dnl # Set Default CFLAGS +-dnl # +-if test "x$GCC" = "xyes"; then +- CFLAGS="$CFLAGS -Wall -D_GNU_SOURCE" +-fi +- +-AC_MSG_CHECKING(for developer gcc flags) +-if test "x$developer" = "xyes" -a "x$GCC" = "xyes"; then +- devflags="-g -Wshadow -Wpointer-arith -Wcast-qual -Wcast-align -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wnested-externs -W -Wredundant-decls -Wundef" +- CFLAGS="$CFLAGS $devflags" +- INSTALLSTRIP="" +- AC_MSG_RESULT(yes. Using $devflags) +-else +- devflags="" +- CFLAGS="$CFLAGS -DNDEBUG" +- INSTALLSTRIP="-s" +- AC_MSG_RESULT(no.) +-fi +- +-dnl ############################################################# +-dnl # +-dnl # 7. Checks for library functions +-dnl # +-dnl ############################################################# +- +-dnl Check for libcrypt +-dnl We use crypt(3) which may be in libc, or in libcrypt (eg FreeBSD) +-AC_CHECK_LIB(crypt, crypt, +- CRYPTLIB="-lcrypt" +-) +-if test "$CRYPTLIB" != ""; then +- AC_DEFINE(HAVE_CRYPT) +-else +- AC_CHECK_FUNC(crypt, AC_DEFINE(HAVE_CRYPT)) +-fi +- +-dnl Check for libcipher +-AC_CHECK_LIB(cipher, setkey, +- CRYPTLIB="${CRYPTLIB} -lcipher" +-) +-AC_SUBST(CRYPTLIB) +- +-if test "x$WITH_SNMP" = "xyes"; then +- SNMP_CHECKS +-fi +- +-dnl Check the style of gethostbyaddr, in order of preference +-dnl GNU (_r eight args) +-dnl SYSV (_r six args) +-dnl BSD (three args, may not be thread safe) +-dnl Tru64 has BSD version, but it is thread safe +-dnl http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V51B_HTML/MAN/MAN3/1739____.HTM +-dnl We need #stdio.h to define NULL on FreeBSD (at least) +-gethostbyaddrrstyle="" +-AC_MSG_CHECKING([gethostbyaddr_r() syntax]) +-case "$host" in +-*-freebsd*) +-dnl With FreeBSD, check if there's a prototype for gethostbyaddr_r. +-dnl Some versions (FreeBSD 5.1?) have a symbol but no prototype - so we +-dnl override this test to BSDSTYLE. FreeBSD 6.2 and up have proper GNU +-dnl style support. +- AC_CHECK_DECLS([gethostbyaddr_r], [], [ +- AC_DEFINE(GETHOSTBYADDRRSTYLE, BSDSTYLE, +- [style of gethostbyaddr_r functions ]) +- gethostbyaddrrstyle=BSD +- AC_MSG_WARN([FreeBSD overridden to BSD-style]) +- ], [ +-#ifdef HAVE_NETDB_H +-#include +-#endif +-]) +- ;; +-esac +-if test "x$gethostbyaddrrstyle" = "x"; then +- AC_TRY_LINK([ +-#include +-#include +-], [ gethostbyaddr_r(NULL, 0, 0, NULL, NULL, 0, NULL, NULL) ], [ +- AC_DEFINE(GETHOSTBYADDRRSTYLE, GNUSTYLE) +- gethostbyaddrrstyle=GNU +-]) +-fi +-if test "x$gethostbyaddrrstyle" = "x"; then +- AC_TRY_LINK([ +-#include +-#include +-], [ gethostbyaddr_r(NULL, 0, 0, NULL, NULL, 0, NULL) ] , [ +- AC_DEFINE(GETHOSTBYADDRRSTYLE, SYSVSTYLE) +- gethostbyaddrrstyle=SYSV +- ]) +-fi +-if test "x$gethostbyaddrrstyle" = "x"; then +- AC_TRY_LINK([ +-#include +-#include +-], [ gethostbyaddr(NULL, 0, 0) ], [ +- AC_DEFINE(GETHOSTBYADDRRSTYLE, BSDSTYLE) +- gethostbyaddrrstyle=BSD +- ]) +-fi +- +-if test "x$gethostbyaddrrstyle" = "x"; then +- AC_MSG_RESULT([none! It must not exist, here.]) +-else +- AC_MSG_RESULT([${gethostbyaddrrstyle}-style]) +-fi +- +-if test "x$gethostbyaddrrstyle" = "xBSD"; then +- AC_MSG_WARN([ ****** BSD-style gethostbyaddr might NOT be thread-safe! ****** ]) +-fi +- +-dnl Check the style of gethostbyname, in order of preference +-dnl GNU (_r seven args) +-dnl SYSV (_r five args) +-dnl BSD (two args, may not be thread safe) +-dnl Tru64 has BSD version, but it _is_ thread safe +-dnl http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V51B_HTML/MAN/MAN3/1946____.HTM +-dnl We need #stdio.h to define NULL on FreeBSD (at least) +-gethostbynamerstyle="" +-AC_MSG_CHECKING([gethostbyname_r() syntax]) +-AC_TRY_LINK([ +-#include +-#include +-], [ gethostbyname_r(NULL, NULL, NULL, 0, NULL, NULL) ], [ +- AC_DEFINE(GETHOSTBYNAMERSTYLE, GNUSTYLE) +- gethostbynamerstyle=GNU +-]) +-if test "x$gethostbynamerstyle" = "x"; then +- AC_TRY_LINK([ +-#include +-#include +-], [ gethostbyname_r(NULL, NULL, NULL, 0, NULL) ] , [ +- AC_DEFINE(GETHOSTBYNAMERSTYLE, SYSVSTYLE) +- gethostbynamerstyle=SYSV +- ]) +-fi +-if test "x$gethostbynamerstyle" = "x"; then +- AC_TRY_LINK([ +-#include +-#include +-], [ gethostbyname(NULL) ], [ +- AC_DEFINE(GETHOSTBYNAMERSTYLE, BSDSTYLE) +- gethostbynamerstyle=BSD +- ]) +-fi +- +-if test "x$gethostbynamerstyle" = "x"; then +- AC_MSG_RESULT([none! It must not exist, here.]) +-else +- AC_MSG_RESULT([${gethostbynamerstyle}-style]) +-fi +- +-if test "x$gethostbynamerstyle" = "xBSD"; then +- AC_MSG_WARN([ ****** BSD-style gethostbyname might NOT be thread-safe! ****** ]) +-fi +- +-dnl check for non-posix solaris ctime_r (extra buflen int arg) +-ctimerstyle="" +-AC_MSG_CHECKING([ctime_r() syntax]) +-AC_TRY_LINK([ +-#include +-], [ ctime_r(NULL, NULL, 0) ], [ +- AC_DEFINE(CTIMERSTYLE, SOLARISSTYLE) +- ctimerstyle="SOLARIS" +-]) +-if test "x$ctimerstyle" = "x"; then +- AC_TRY_LINK([ +-#include +-], [ ctime_r(NULL, NULL) ], [ +- AC_DEFINE(CTIMERSTYLE, POSIXSTYLE) +- ctimerstyle="POSIX" +- ]) +-fi +- +-if test "x$ctimerstyle" = "x"; then +- AC_MSG_RESULT([none! It must not exist, here.]) +-else +- AC_MSG_RESULT([${ctimerstyle}-style]) +-fi +- +- +-dnl If configuring with large file support, determine the right flags to +-dnl use based on the platform. This is the wrong approach; autoconf 2.50 +-dnl comes with a macro that takes the right approach. But this works well +-dnl enough until we switch to autoconf 2.50 or later. +-if test x"$rad_enable_largefiles" = xyes ; then +- AC_MSG_CHECKING(for largefile linkage) +- case "$host" in +- *-aix4.[01]*) +- AC_MSG_RESULT(no) +- AC_MSG_ERROR([AIX before 4.2 does not support large files]) +- ;; +- *-aix4*) +- AC_MSG_RESULT(ok) +- LFS_CFLAGS="-D_LARGE_FILES" +- LFS_LDFLAGS="" +- LFS_LIBS="" +- ;; +- *-hpux*) +- AC_MSG_RESULT(ok) +- LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" +- LFS_LDFLAGS="" +- LFS_LIBS="" +- ;; +- *-irix*) +- AC_MSG_RESULT(no) +- AC_MSG_ERROR([Large files not supported on this platform]) +- ;; +- *-linux*) +- AC_MSG_RESULT(maybe) +- LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" +- LFS_LDFLAGS="" +- LFS_LIBS="" +- AC_DEFINE([_GNU_SOURCE], 1, +- [Some versions of glibc need this defined for pread/pwrite.]) +- ;; +- *-solaris*) +- AC_MSG_RESULT(ok) +- AC_PATH_PROG(GETCONF, getconf) +- if test -z "$GETCONF" ; then +- AC_MSG_ERROR([getconf required to configure large file support]) +- fi +- LFS_CFLAGS=`$GETCONF LFS_CFLAGS` +- LFS_LDFLAGS=`$GETCONF LFS_LDFLAGS` +- LFS_LIBS=`$GETCONF LFS_LIBS` +- ;; +- *) +- AC_MSG_RESULT(maybe) +- LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64" +- LFS_LDFLAGS="" +- LFS_LIBS="" +- ;; +- esac +- AC_SUBST(LFS_CFLAGS) +- AC_SUBST(LFS_LDFLAGS) +- AC_SUBST(LFS_LIBS) +-fi +- +-AC_SUBST(HOSTINFO, $host) +- +-dnl ############################################################# +-dnl # +-dnl # 8. Checks for system services +-dnl # +-dnl ############################################################# +- +-dnl # +-dnl # Figure out where libtool is located, +-dnl # +-top_builddir=`pwd` +-export top_builddir +-AC_MSG_RESULT([top_builddir=$top_builddir]) +-dnl # AC_SUBST(top_builddir) +-AC_SUBST(LIBLTDL) +-AC_SUBST(INCLTDL) +- +-dnl import libtool stuff +- +-dnl ############################################################# +-dnl # +-dnl # Configure in any module directories. +-dnl # +-dnl ############################################################# +- +-mysubdirs= +-if test "x$EXPERIMENTAL" = "xyes"; then +- bar=`ls -1 src/modules/rlm_*/configure | sed 's%/configure%%'` +- dnl # get rid of LF's. +- mysubdirs=`echo $bar` +-else +- dnl # +- dnl # Find 'configure' in ONLY the stable modules +- dnl # +- for bar in `cat src/modules/stable`; do +- if test -f src/modules/$bar/configure; then +- mysubdirs="$mysubdirs src/modules/$bar" +- fi +- done +-fi +- +-dnl ############################################################ +-dnl # make modules by list +-dnl ############################################################# +-if test "x$EXPERIMENTAL" = "xyes"; then +- for foo in `ls -1 src/modules | grep rlm_`; do +- MODULES="$MODULES $foo" +- done +-else +- dnl # +- dnl # make ONLY the stable modules +- dnl # +- for foo in `cat src/modules/stable`; do +- MODULES="$MODULES $foo" +- done +-fi +- +-dnl # +-dnl # Don't change the variable name here. Autoconf goes bonkers +-dnl # if you do. +-dnl # +-AC_CONFIG_SUBDIRS($LTDL_SUBDIRS $mysubdirs) +-AC_SUBST(MODULES) +- +-dnl ############################################################# +-dnl # +-dnl # And finally, output the results. +-dnl # +-dnl ############################################################# +- +-dnl # +-dnl # Substitute whatever libraries we found to be necessary +-dnl # +-AC_SUBST(LIBS) +-AC_SUBST(INSTALLSTRIP) +- +-USE_SHARED_LIBS=$enable_shared +-AC_SUBST(USE_SHARED_LIBS) +-USE_STATIC_LIBS=$enable_static +-AC_SUBST(USE_STATIC_LIBS) +-AC_SUBST(STATIC_MODULES) +-AC_SUBST(RADIUSD_MAJOR_VERSION) +-AC_SUBST(RADIUSD_MINOR_VERSION) +-AC_SUBST(RADIUSD_VERSION) +-export CFLAGS LIBS +- +-AC_OUTPUT(\ +- ./Make.inc \ +- ./src/include/build-radpaths-h \ +- ./src/main/Makefile \ +- ./src/main/checkrad.pl \ +- ./src/main/radlast \ +- ./src/main/radtest \ +- ./scripts/rc.radiusd \ +- ./scripts/radwatch \ +- ./scripts/check-radiusd-config \ +- ./scripts/radiusd.cron.daily \ +- ./scripts/radiusd.cron.monthly \ +- ./scripts/cryptpasswd \ +- ./raddb/dictionary \ +- ./raddb/radiusd.conf +-) +- +-AC_OUTPUT_COMMANDS([echo timestamp > src/include/stamp-h]) +-AC_OUTPUT_COMMANDS([(cd ./src/include && /bin/sh ./build-radpaths-h)]) +-AC_OUTPUT_COMMANDS([(cd ./src/main && chmod +x checkrad.pl radlast radtest)]) +-AC_OUTPUT_COMMANDS([(cd ./scripts && chmod +x rc.radiusd radwatch check-radiusd-config radiusd.cron.daily radiusd.cron.monthly cryptpasswd)]) +diff -Naurp freeradius-server_orig/src/modules/rlm_eap/Makefile freeradius-server/src/modules/rlm_eap/Makefile +--- freeradius-server_orig/src/modules/rlm_eap/Makefile 1969-12-31 16:00:00.000000000 -0800 ++++ freeradius-server/src/modules/rlm_eap/Makefile 2021-03-26 13:34:01.181050000 -0700 +@@ -0,0 +1,49 @@ ++# ++# $Id: Makefile.in,v 1.12.2.2.2.4 2006/05/25 16:24:41 nbk Exp $ ++# ++ ++# TARGET = rlm_eap ++# SRCS = rlm_eap.c eap.c mem.c state.c ++HEADERS = eap.h rlm_eap.h ++RLM_CFLAGS = $(INCLTDL) -Ilibeap ++CLIENTLIBS = libeap/libeap.la ++RLM_LIBS = libeap/libeap.la $(LIBLTDL) ++RLM_INSTALL = install-subdirs ++RLM_SUBDIRS = libeap ++RLM_UTILS = radeapclient ++ ++# ++# Not using shared libraries, add in ALL known static modules ++# at build time. ++# ++#ifneq ($(USE_SHARED_LIBS),yes) ++#RLM_LIBS += $(shell for x in types/rlm_eap*/rlm_eap*.la;do echo -dlpreopen $$x;done) ++#endif ++ ++.PHONY: all install-subdirs common ++ ++# ++# We need libeap.la before we can build anything else ++# ++all: common ++ ++$(LT_OBJS): $(HEADERS) ++ ++radeapclient: radeapclient.lo $(CLIENTLIBS) ++ $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) $(RLM_LDFLAGS) -o radeapclient radeapclient.lo $(CLIENTLIBS) $(LIBS) $(OPENSSL_LIBS) ++ ++radeapclient.lo: radeapclient.c $(HEADERS) ++ $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(RLM_CFLAGS) -c radeapclient.c ++ ++install-subdirs: ++ @$(MAKE) $(MFLAGS) WHAT_TO_MAKE=install common ++ $(LIBTOOL) --mode=install $(INSTALL) -m 755 $(INSTALLSTRIP) radeapclient$(EXEEXT) $(R)$(bindir) ++ ++common: ++ @for dir in libeap; do \ ++ echo "Making $(WHAT_TO_MAKE) in $$dir..."; \ ++ $(MAKE) $(MFLAGS) -C $$dir $(WHAT_TO_MAKE) || exit $$?; \ ++ done ++ ++## this uses the RLM_CFLAGS and RLM_LIBS and SRCS defs to make TARGET. ++include ../rules.mak diff --git a/src/radius/pam/freeradius/patches/freeradius_libeap_deprecated_openssl_1_0.patch b/src/radius/pam/freeradius/patches/freeradius_libeap_deprecated_openssl_1_0.patch new file mode 100644 index 000000000000..2c1117e6a295 --- /dev/null +++ b/src/radius/pam/freeradius/patches/freeradius_libeap_deprecated_openssl_1_0.patch @@ -0,0 +1,130 @@ +diff -Naurp freeradius-1.1.4_orig/src/modules/rlm_eap/libeap/mppe_keys.c freeradius-1.1.4/src/modules/rlm_eap/libeap/mppe_keys.c +--- freeradius-1.1.4_orig/src/modules/rlm_eap/libeap/mppe_keys.c 2019-11-05 17:47:48.118829000 -0800 ++++ freeradius-1.1.4/src/modules/rlm_eap/libeap/mppe_keys.c 2019-11-05 19:23:35.176930000 -0800 +@@ -52,47 +52,55 @@ static void P_hash(const EVP_MD *evp_md, + const unsigned char *seed, unsigned int seed_len, + unsigned char *out, unsigned int out_len) + { +- HMAC_CTX ctx_a, ctx_out; ++ HMAC_CTX * ctx_a, * ctx_out; + unsigned char a[HMAC_MAX_MD_CBLOCK]; + unsigned int size; + +- HMAC_CTX_init(&ctx_a); +- HMAC_CTX_init(&ctx_out); +- HMAC_Init_ex(&ctx_a, secret, secret_len, evp_md, NULL); +- HMAC_Init_ex(&ctx_out, secret, secret_len, evp_md, NULL); ++ if (NULL == (ctx_a = HMAC_CTX_new())) { ++ DEBUG("P_hash: HMAC_CTX_new() failed\n"); ++ goto P_hash_exit; ++ } ++ if (NULL == (ctx_out = HMAC_CTX_new())) { ++ DEBUG("P_hash: HMAC_CTX_new() failed\n"); ++ goto P_hash_exit; ++ } ++ ++ HMAC_Init_ex(ctx_a, secret, secret_len, evp_md, NULL); ++ HMAC_Init_ex(ctx_out, secret, secret_len, evp_md, NULL); + +- size = HMAC_size(&ctx_out); ++ size = HMAC_size(ctx_out); + + /* Calculate A(1) */ +- HMAC_Update(&ctx_a, seed, seed_len); +- HMAC_Final(&ctx_a, a, NULL); ++ HMAC_Update(ctx_a, seed, seed_len); ++ HMAC_Final(ctx_a, a, NULL); + + while (1) { + /* Calculate next part of output */ +- HMAC_Update(&ctx_out, a, size); +- HMAC_Update(&ctx_out, seed, seed_len); ++ HMAC_Update(ctx_out, a, size); ++ HMAC_Update(ctx_out, seed, seed_len); + + /* Check if last part */ + if (out_len < size) { +- HMAC_Final(&ctx_out, a, NULL); ++ HMAC_Final(ctx_out, a, NULL); + memcpy(out, a, out_len); + break; + } + + /* Place digest in output buffer */ +- HMAC_Final(&ctx_out, out, NULL); +- HMAC_Init_ex(&ctx_out, NULL, 0, NULL, NULL); ++ HMAC_Final(ctx_out, out, NULL); ++ HMAC_Init_ex(ctx_out, NULL, 0, NULL, NULL); + out += size; + out_len -= size; + + /* Calculate next A(i) */ +- HMAC_Init_ex(&ctx_a, NULL, 0, NULL, NULL); +- HMAC_Update(&ctx_a, a, size); +- HMAC_Final(&ctx_a, a, NULL); ++ HMAC_Init_ex(ctx_a, NULL, 0, NULL, NULL); ++ HMAC_Update(ctx_a, a, size); ++ HMAC_Final(ctx_a, a, NULL); + } + +- HMAC_CTX_cleanup(&ctx_a); +- HMAC_CTX_cleanup(&ctx_out); ++P_hash_exit: ++ if (ctx_a) HMAC_CTX_free(ctx_a); ++ if (ctx_out) HMAC_CTX_free(ctx_out); + memset(a, 0, sizeof(a)); + } + +@@ -127,21 +135,25 @@ void eaptls_gen_mppe_keys(VALUE_PAIR **r + unsigned char seed[64 + 2*SSL3_RANDOM_SIZE]; + unsigned char *p = seed; + size_t prf_size; ++ unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; ++ size_t master_key_len; + + prf_size = strlen(prf_label); + + memcpy(p, prf_label, prf_size); + p += prf_size; + +- memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); ++ SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); + p += SSL3_RANDOM_SIZE; + prf_size += SSL3_RANDOM_SIZE; + +- memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); ++ SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); + prf_size += SSL3_RANDOM_SIZE; + +- PRF(s->session->master_key, s->session->master_key_length, +- seed, prf_size, out, buf, sizeof(out)); ++ master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), ++ master_key, sizeof(master_key)); ++ ++ PRF(master_key, master_key_len, seed, prf_size, out, buf, sizeof(out)); + + p = out; + add_reply(reply_vps, "MS-MPPE-Recv-Key", p, EAPTLS_MPPE_KEY_LEN); +@@ -163,14 +175,19 @@ void eapttls_gen_challenge(SSL *s, char + unsigned char out[32], buf[32]; + unsigned char seed[sizeof(EAPTLS_PRF_CHALLENGE)-1 + 2*SSL3_RANDOM_SIZE]; + unsigned char *p = seed; ++ unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; ++ size_t master_key_len; + + memcpy(p, EAPTLS_PRF_CHALLENGE, sizeof(EAPTLS_PRF_CHALLENGE)-1); + p += sizeof(EAPTLS_PRF_CHALLENGE)-1; +- memcpy(p, s->s3->client_random, SSL3_RANDOM_SIZE); ++ SSL_get_client_random(s, p, SSL3_RANDOM_SIZE); + p += SSL3_RANDOM_SIZE; +- memcpy(p, s->s3->server_random, SSL3_RANDOM_SIZE); ++ SSL_get_server_random(s, p, SSL3_RANDOM_SIZE); ++ ++ master_key_len = SSL_SESSION_get_master_key(SSL_get_session(s), ++ master_key, sizeof(master_key)); + +- PRF(s->session->master_key, s->session->master_key_length, ++ PRF(master_key, master_key_len, + seed, sizeof(seed), out, buf, sizeof(out)); + + memcpy(buffer, out, size); diff --git a/src/radius/pam/patches/0001-chap-support.patch b/src/radius/pam/patches/0001-chap-support.patch new file mode 100644 index 000000000000..91af430ffcab --- /dev/null +++ b/src/radius/pam/patches/0001-chap-support.patch @@ -0,0 +1,133 @@ +Index: pam_radius/src/pam_radius_auth.c +=================================================================== +--- pam_radius.orig/src/pam_radius_auth.c ++++ pam_radius/src/pam_radius_auth.c +@@ -130,6 +130,15 @@ static int _pam_parse(int argc, CONST ch + } else if (!strcmp(*argv, "privilege_level")) { + conf->privilege_level = TRUE; + ++ } else if (!strncmp(*argv, "protocol=", 9)) { ++ if (!strncmp((char *)*argv+9, "pap", 4)) { ++ conf->auth_type = AUTH_TYPE_PAP; ++ } else if (!strncmp((char *)*argv+9, "chap", 5)) { ++ conf->auth_type = AUTH_TYPE_CHAP; ++ } else { ++ _pam_log(LOG_WARNING, "ignoring '%s'", *argv); ++ } ++ + } else { + _pam_log(LOG_WARNING, "unrecognized option '%s'", *argv); + } +@@ -510,6 +519,61 @@ static void add_password(AUTH_HDR *reque + } + } + ++/* ++ * int add_chap_password() ++ * ++ * Description: ++ * Add a RADIUS CHAP-Password attribute to the packet. ++ * ++ * This uses the input password as the secret to construct the ++ * CHAP response. The Request Authenticator field is used as the ++ * CHAP challenge. Compute the response and send as CHAP-Password ++ * in the Access-Request. ++ * ++ * Input(s): ++ * request - pointer to RADIUS request header ++ * chap_id - CHAP ID ++ * password - password string to do CHAP ++ * ++ * Output(s): ++ * request - pointer to RADIUS request header ++ * ++ */ ++static void add_chap_password(AUTH_HDR *request, unsigned char chap_id, CONST char *password) ++{ ++ unsigned char resp[CHAP_VALUE_LENGTH+1]; ++ unsigned char string[MAXPASS+AUTH_VECTOR_LEN+1], *ptr = string; ++ int length; ++ MD5_CTX md5_context; ++ attribute_t *attr; ++ ++ length = strlen(password); ++ if (length > MAXPASS) { /* shorten the password for now */ ++ _pam_log(LOG_WARNING, "invalid password length: %d. Shortening", length); ++ length = MAXPASS; ++ } ++ ++ *ptr++ = chap_id; ++ memcpy(ptr, password, length); ++ ptr += length; ++ memcpy(ptr, request->vector, AUTH_VECTOR_LEN); ++ ptr += AUTH_VECTOR_LEN; ++ ++ resp[0] = chap_id; ++ MD5Init(&md5_context); ++ MD5Update(&md5_context, string, ptr - string); ++ MD5Final(resp+1, &md5_context); ++ ++ attr = find_attribute(request, PW_CHAP_PASSWORD); ++ if (!attr) { ++ add_attribute(request, PW_CHAP_PASSWORD, resp, AUTH_VECTOR_LEN+1); ++ } else { ++ memcpy(attr->data, resp, AUTH_VECTOR_LEN+1); ++ } ++ ++ memset( string, 0, ptr - string); /* Scrub (zeroize) it */ ++} ++ + static void cleanup(radius_server_t *server) + { + radius_server_t *next; +@@ -804,7 +868,12 @@ static void build_radius_packet(AUTH_HDR + * Add a password, if given. + */ + if (password) { +- add_password(request, PW_PASSWORD, password, conf->server->secret); ++ if ((request->code == PW_AUTHENTICATION_REQUEST) ++ && (conf->auth_type == AUTH_TYPE_CHAP)) { ++ add_chap_password(request, request->vector[0], password); ++ } else { ++ add_password(request, PW_PASSWORD, password, conf->server->secret); ++ } + + /* + * Add a NULL password to non-accounting requests. +@@ -1075,7 +1144,12 @@ static int talk_radius(radius_conf_t *co + 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); ++ if ((request->code == PW_AUTHENTICATION_REQUEST) ++ && (conf->auth_type == AUTH_TYPE_CHAP)) { ++ add_chap_password(request, request->vector[0], password); ++ } else { ++ add_password(request, PW_PASSWORD, password, server->secret); ++ } + } + } + } +Index: pam_radius/src/pam_radius_auth.h +=================================================================== +--- pam_radius.orig/src/pam_radius_auth.h ++++ pam_radius/src/pam_radius_auth.h +@@ -123,6 +123,10 @@ + #endif + + ++/* Authentication Protocol types. */ ++#define AUTH_TYPE_PAP 0 ++#define AUTH_TYPE_CHAP 1 ++ + /************************************************************************* + * Additional RADIUS definitions + *************************************************************************/ +@@ -162,6 +166,7 @@ typedef struct radius_conf_t { + char prompt[MAXPROMPT]; + int prompt_attribute; + int privilege_level; ++ int auth_type; + } radius_conf_t; + + #endif /* PAM_RADIUS_H */ diff --git a/src/radius/pam/patches/0002-peap-mschapv2-support.patch b/src/radius/pam/patches/0002-peap-mschapv2-support.patch new file mode 100644 index 000000000000..eae79abeeed7 --- /dev/null +++ b/src/radius/pam/patches/0002-peap-mschapv2-support.patch @@ -0,0 +1,4631 @@ +Index: pam_radius/src/pam_radius_auth.h +=================================================================== +--- pam_radius.orig/src/pam_radius_auth.h ++++ pam_radius/src/pam_radius_auth.h +@@ -126,6 +126,10 @@ + /* Authentication Protocol types. */ + #define AUTH_TYPE_PAP 0 + #define AUTH_TYPE_CHAP 1 ++#define AUTH_TYPE_MSCHAPV2 2 ++ ++/* Also in radpeapclient.h */ ++#define PAM_AUTH_SECUREID -2 + + /************************************************************************* + * Additional RADIUS definitions +Index: pam_radius/src/pam_radius_auth.c +=================================================================== +--- pam_radius.orig/src/pam_radius_auth.c ++++ pam_radius/src/pam_radius_auth.c +@@ -24,6 +24,14 @@ + * All rights reserved. + */ + ++/* ++ * Changes for CHAP and PEAP-MSCHAPV2 added. ~ November, 2019 ++ * ++ * Copyright 2019 Broadcom. All rights reserved. ++ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. ++ * ++ */ ++ + #define PAM_SM_AUTH + #define PAM_SM_PASSWORD + #define PAM_SM_SESSION +@@ -135,6 +143,8 @@ static int _pam_parse(int argc, CONST ch + conf->auth_type = AUTH_TYPE_PAP; + } else if (!strncmp((char *)*argv+9, "chap", 5)) { + conf->auth_type = AUTH_TYPE_CHAP; ++ } else if (!strncmp((char *)*argv+9, "mschapv2", 9)) { ++ conf->auth_type = AUTH_TYPE_MSCHAPV2; + } else { + _pam_log(LOG_WARNING, "ignoring '%s'", *argv); + } +@@ -951,6 +961,58 @@ static int talk_radius(radius_conf_t *co + goto next; /* skip to the next server */ + } + ++ /* ++ * Check whether the authentication selected is PEAP ++ */ ++ if ((AUTH_TYPE_MSCHAPV2 == conf->auth_type) && ++ (PW_AUTHENTICATION_REQUEST == request->code)) { ++ char user_name[MAXPWNAM] = {'\0'}; ++ attribute_t * peap_attr = 0; ++ ++ peap_attr = find_attribute(request, PW_USER_NAME); ++ if (!peap_attr) { ++ _pam_log(LOG_DEBUG, ++ "attribute type: %u not found", ++ PW_USER_NAME); ++ return PAM_CRED_ERR; ++ } ++ strncpy(user_name, (char *) (peap_attr->data), ++ peap_attr->length - sizeof(short) ); ++ user_name [peap_attr->length - sizeof(short)] = '\0'; ++ _pam_log(LOG_DEBUG,"talk_radius: Auth type is PEAP \n"); ++ ++ retval = pam_peap_authenticate(user_name, password, ++ conf, response); ++ ++ if( 0 == retval) { ++ _pam_log(LOG_DEBUG, ++ "talk_radius: PEAP authentication successful \n"); ++ return PAM_SUCCESS; ++ } else if (PAM_AUTHINFO_UNAVAIL == retval) { ++ ok = FALSE; ++ goto next; /* server failed to respond */ ++ } else { ++ /* ++ * User authentication failed. ++ * The radius log will show the ++ * information on the failure ++ */ ++ /* Return on access reject from Radius server ++ */ ++ _pam_log(LOG_DEBUG, ++ "talk_radius: peap authentication failed for the user %s \n", ++ user_name); ++ if (retval == PAM_AUTH_SECUREID) { ++ _pam_log(LOG_DEBUG, ++ "talk_radius: peap authentication %d \n", retval); ++ retval = PAM_AUTH_ERR; ++ } else { ++ return (PAM_AUTH_ERR); ++ } ++ } ++ } ++ ++ + if (!password) { /* make an RFC 2139 p6 request authenticator */ + get_accounting_vector(request, server); + } +Index: pam_radius/xsupplicant_mschapv2_COPYING +=================================================================== +--- /dev/null ++++ pam_radius/xsupplicant_mschapv2_COPYING +@@ -0,0 +1,52 @@ ++ XSupplicant -- A client-side 802.1x implementation ++ ++ This code is released under both the GPL version 2 and BSD licenses. ++ Either license may be used. The respective licenses are found below. ++ ++ Copyright (C) 2002 Bryan D. Payne & Nick L. Petroni Jr. ++ Copyright (C) 2003, 2004 The Open1x Team ++ All Rights Reserved ++ ++ --- GPL Version 2 License --- ++ 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. ++ ++ --- BSD License --- ++ Redistribution and use in source and binary forms, with or without ++ modification, are permitted provided that the following conditions are met: ++ ++ - Redistributions of source code must retain the above copyright notice, ++ this list of conditions and the following disclaimer. ++ - Redistributions in binary form must reproduce the above copyright ++ notice, this list of conditions and the following disclaimer in the ++ documentation and/or other materials provided with the distribution. ++ - All advertising materials mentioning features or use of this software ++ must display the following acknowledgement: ++ This product includes software developed by the University of ++ Maryland at College Park, the Open1x team, and its contributors. ++ - Neither the name of the University or Open1x team, nor the names ++ of its contributors may be used to endorse or promote products derived ++ from this software without specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE ++ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++ POSSIBILITY OF SUCH DAMAGE. +Index: pam_radius/src/mschapv2.c +=================================================================== +--- /dev/null ++++ pam_radius/src/mschapv2.c +@@ -0,0 +1,625 @@ ++/******************************************************************* ++ * EAPMSCHAPv2 Function implementations ++ * ++ * Licensed under a dual GPL/BSD license. (See LICENSE file for more info.) ++ * ++ * File: mschapv2.c ++ * ++ * Authors: Chris.Hessing@utah.edu ++ * ++ * $Id: mschapv2.c,v 1.16 2006/06/01 22:49:50 galimorerpg Exp $ ++ * $Date: 2006/06/01 22:49:50 $ ++ * $Log: mschapv2.c,v $ ++ * Revision 1.16 2006/06/01 22:49:50 galimorerpg ++ * Converted all instances of u_char to uint8_t ++ * Fixed a bad #include in the generic frame handler. ++ * ++ * Revision 1.15 2006/05/29 04:17:58 chessing ++ * Fixes for some memory leaks. ++ * ++ * Revision 1.14 2006/04/25 01:17:43 chessing ++ * LOTS of code cleanups, new error checking/debugging code added, and other misc. fixes/changes. ++ * ++ * Revision 1.13 2006/01/03 04:02:35 chessing ++ * Added the ability to store the PEAP password in a hashed format. (Basically, an MS-CHAPv1 hash.) Also added an 'ntpwdhash' program to the tools directory that will convert a cleartext password in to a hash that can be copied to the configuration file. ++ * ++ * Revision 1.12 2005/10/14 02:26:18 shaftoe ++ * - cleanup gcc 4 warnings ++ * - (re)add support for a pid in the form of /var/run/xsupplicant..pid ++ * ++ * -- Eric Evans ++ * ++ * Revision 1.11 2005/08/09 01:39:16 chessing ++ * Cleaned out old commit notes from the released version. Added a few small features including the ability to disable the friendly warnings that are spit out. (Such as the warning that is displayed when keys aren't rotated after 10 minutes.) We should also be able to start when the interface is down. Last, but not least, we can handle empty network configs. (This may be useful for situations where there isn't a good reason to have a default network defined.) ++ * ++ * ++ *******************************************************************/ ++ ++/* Modified for OpenSSL 1.1.0. ~ November, 2019 ++ * ++ * Copyright 2019 Broadcom. All rights reserved. ++ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. ++ */ ++ ++// This code was taken from the pseudo code in RFC 2759. ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#if 0 ++#include "../../xsup_debug.h" ++#include "../../xsup_err.h" ++#include "../../profile.h" ++#else ++#define xsup_assert(x,y,z) 1 ++#define debug_printf(...) ++#define debug_hex_printf(...) ++#endif ++ ++#ifdef USE_EFENCE ++#include ++#endif ++ ++void ChallengeHash(char *PeerChallenge, char *AuthenticatorChallenge, ++ char *UserName, char *Challenge) ++{ ++ EVP_MD_CTX * cntx = EVP_MD_CTX_new(); ++ char Digest[30]; ++ int retLen; ++ ++ if (cntx == NULL) ++ return; ++ ++ if (!xsup_assert((PeerChallenge != NULL), "PeerChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((AuthenticatorChallenge != NULL), ++ "AuthenticatorChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((UserName != NULL), "UserName != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((Challenge != NULL), "Challenge != NULL", FALSE)) ++ return; ++ ++ bzero(Digest, 30); ++ EVP_DigestInit(cntx, EVP_sha1()); ++ EVP_DigestUpdate(cntx, PeerChallenge, 16); ++ EVP_DigestUpdate(cntx, AuthenticatorChallenge, 16); ++ EVP_DigestUpdate(cntx, UserName, strlen(UserName)); ++ EVP_DigestFinal(cntx, (uint8_t *)&Digest, (u_int *) &retLen); ++ ++ EVP_MD_CTX_free(cntx); ++ ++ memcpy(Challenge, Digest, 8); ++} ++ ++char *to_unicode(char *non_uni) ++{ ++ char *retUni; ++ int i; ++ ++ if (!xsup_assert((non_uni != NULL), "non_uni != NULL", FALSE)) ++ return NULL; ++ ++ retUni = (char *)malloc((strlen(non_uni)+1)*2); ++ if (retUni == NULL) ++ { ++ debug_printf(DEBUG_NORMAL, "Error with MALLOC in to_unicode()!\n"); ++ return NULL; ++ } ++ bzero(retUni, ((strlen(non_uni)+1)*2)); ++ ++ for (i=0; i> i) | next | 1; ++ next = tmp << (7-i); ++ } ++ pkey[i] = next | 1; ++ ++ DES_set_key(&pkey, &ks); ++ DES_ecb_encrypt((DES_cblock *) clear, (DES_cblock *) cypher, &ks, ++ DES_ENCRYPT); ++} ++ ++char ctonibble(char cnib) ++{ ++ char retVal=0x00; ++ char testval=0x00; ++ ++ if ((cnib>='0') && (cnib<='9')) ++ { ++ retVal = cnib - '0'; ++ } else { ++ testval = toupper(cnib); ++ if ((testval>='A') && (testval<='F')) ++ { ++ retVal = ((testval - 'A') +10); ++ } else { ++ debug_printf(DEBUG_NORMAL, "Error in conversion! (Check ctonibble()) -- %02x\n",testval); ++ } ++ } ++ return retVal; ++} ++ ++// Convert an ASCII string to a binary version of it. ++void process_hex(char *instr, int size, char *outstr) ++{ ++ int i; ++ ++ if (!xsup_assert((instr != NULL), "instr != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((outstr != NULL), "outstr != NULL", FALSE)) ++ return; ++ ++ // Make sure we don't try to convert something that isn't byte aligned. ++ if ((size % 2) != 0) ++ { ++ debug_printf(DEBUG_NORMAL, "Hex string isn't an even number of chars!!!" ++ "\n"); ++ return; ++ } ++ ++ for (i=0;i<(size/2);i++) ++ { ++ if (instr[i*2] != 0x00) ++ { ++ outstr[i] = (ctonibble(instr[i*2]) << 4) + ctonibble(instr[(i*2)+1]); ++ } ++ } ++} ++ ++void GenerateAuthenticatorResponse(char *Password, char *NTResponse, ++ char *PeerChallenge, ++ char *AuthenticatorChallenge, char *UserName, ++ char *AuthenticatorResponse, int nthash) ++{ ++ char PasswordHash[16]; ++ char PasswordHashHash[16]; ++ EVP_MD_CTX * cntx = EVP_MD_CTX_new(); ++ int Digest_len; ++ char Digest[20]; ++ char Challenge[8]; ++ ++ char Magic1[39] = ++ {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, ++ 0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65, ++ 0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67, ++ 0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74}; ++ ++ char Magic2[41] = ++ {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B, ++ 0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F, ++ 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E, ++ 0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, ++ 0x6E}; ++ ++ if (cntx == NULL) ++ return; ++ ++ if (!xsup_assert((Password != NULL), "Password != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((NTResponse != NULL), "NTResponse != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((PeerChallenge != NULL), "PeerChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((AuthenticatorChallenge != NULL), ++ "AuthenticatorChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((UserName != NULL), "UserName != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((AuthenticatorResponse != NULL), ++ "AuthenticatorResponse != NULL", FALSE)) ++ return; ++ ++ if (nthash == 0) ++ { ++ NtPasswordHash(Password, (char *)&PasswordHash); ++ } else { ++ process_hex(Password, strlen(Password), (char *)&PasswordHash); ++ } ++ ++ HashNtPasswordHash((char *)&PasswordHash, (char *)&PasswordHashHash); ++ ++ EVP_DigestInit(cntx, EVP_sha1()); ++ EVP_DigestUpdate(cntx, &PasswordHashHash, 16); ++ EVP_DigestUpdate(cntx, NTResponse, 24); ++ EVP_DigestUpdate(cntx, Magic1, 39); ++ EVP_DigestFinal(cntx, (uint8_t *)&Digest, (u_int *) &Digest_len); ++ ++ ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, Challenge); ++ ++ EVP_DigestInit(cntx, EVP_sha1()); ++ EVP_DigestUpdate(cntx, &Digest, 20); ++ EVP_DigestUpdate(cntx, &Challenge, 8); ++ EVP_DigestUpdate(cntx, Magic2, 41); ++ EVP_DigestFinal(cntx, (uint8_t *)&Digest, (u_int *) &Digest_len); ++ ++ EVP_MD_CTX_free(cntx); ++ ++ memcpy(AuthenticatorResponse, &Digest, Digest_len); ++} ++ ++ ++ ++void CheckAuthenticatorResponse(char *Password, char *NtResponse, ++ char *PeerChallenge, ++ char *AuthenticatorChallenge, char *UserName, ++ char *ReceivedResponse, int *ResponseOK, ++ int nthash) ++{ ++ char MyResponse[20], procResp[20]; ++ ++ if (!xsup_assert((Password != NULL), "Password != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((NtResponse != NULL), "NtResponse != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((PeerChallenge != NULL), "PeerChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((AuthenticatorChallenge != NULL), ++ "AuthenticatorChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((UserName != NULL), "UserName != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((ReceivedResponse != NULL), "ReceivedResponse != NULL", ++ FALSE)) return; ++ ++ if (!xsup_assert((ResponseOK != NULL), "ResponseOK != NULL", FALSE)) ++ return; ++ ++ GenerateAuthenticatorResponse(Password, NtResponse, PeerChallenge, ++ AuthenticatorChallenge, UserName, ++ (char *)&MyResponse, nthash); ++ ++ process_hex(ReceivedResponse, strlen(ReceivedResponse), (char *)&procResp); ++ ++ if (memcmp((char *)&MyResponse, (char *)&procResp, 20) == 0) ++ { ++ *ResponseOK = 1; ++ } else { ++ *ResponseOK = 0; ++ } ++} ++ ++// Take from hostap code by Jouni Malinen, and modified to work with ++// XSupplicant. ++void ChallengeResponse(char *Challenge, char *PasswordHash, char *Response) ++{ ++ uint8_t zpwd[7]; ++ ++ if (!xsup_assert((Challenge != NULL), "Challenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((PasswordHash != NULL), "PasswordHash != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((Response != NULL), "Response != NULL", FALSE)) ++ return; ++ ++ des_encrypt((uint8_t *) Challenge, (uint8_t *) PasswordHash, (uint8_t *) Response); ++ des_encrypt((uint8_t *) Challenge, (uint8_t *) PasswordHash + 7, (uint8_t *) Response+8); ++ zpwd[0] = PasswordHash[14]; ++ zpwd[1] = PasswordHash[15]; ++ memset(zpwd + 2, 0, 5); ++ des_encrypt((uint8_t *) Challenge, zpwd, (uint8_t *) Response+16); ++} ++ ++void NtChallengeResponse(char *Challenge, char *Password, char *Response, ++ int nthash) ++{ ++ char password_hash[16]; ++ ++ if (!xsup_assert((Challenge != NULL), "Challenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((Password != NULL), "Password != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((Response != NULL), "Response != NULL", FALSE)) ++ return; ++ ++ if (nthash == 0) ++ { ++ NtPasswordHash(Password, (char *)&password_hash); ++ } else { ++ process_hex(Password, strlen(Password), (char *)&password_hash); ++ } ++ ChallengeResponse(Challenge, (char *)&password_hash, Response); ++} ++ ++void GenerateNTResponse(char *AuthenticatorChallenge, char *PeerChallenge, ++ char *UserName, char *Password, char *Response, ++ int nthash) ++{ ++ char Challenge[8], PasswordHash[16]; ++ ++ if (!xsup_assert((AuthenticatorChallenge != NULL), ++ "AuthenticatorChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((PeerChallenge != NULL), "PeerChallenge != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((UserName != NULL), "UserName != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((Password != NULL), "Password != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((Response != NULL), "Response != NULL", FALSE)) ++ return; ++ ++ ChallengeHash(PeerChallenge, AuthenticatorChallenge, UserName, (char *)&Challenge); ++ debug_printf(DEBUG_AUTHTYPES, "PeerChallenge : "); ++ debug_hex_printf(DEBUG_AUTHTYPES, (uint8_t *) PeerChallenge, 8); ++ debug_printf(DEBUG_AUTHTYPES, "AuthenticatorChallenge : "); ++ debug_hex_printf(DEBUG_AUTHTYPES, (uint8_t *) AuthenticatorChallenge, 8); ++ debug_printf(DEBUG_AUTHTYPES, "Username : %s\n",UserName); ++ debug_printf(DEBUG_AUTHTYPES, "Challenge : "); ++ debug_hex_printf(DEBUG_AUTHTYPES, (uint8_t *) Challenge, 8); ++ ++ if (nthash == 0) ++ { ++ NtPasswordHash(Password, (char *)&PasswordHash); ++ } else { ++ process_hex(Password, strlen(Password), (char *)&PasswordHash); ++ } ++ ++ debug_printf(DEBUG_AUTHTYPES, "PasswordHash : "); ++ debug_hex_printf(DEBUG_AUTHTYPES, (uint8_t *) PasswordHash, 16); ++ ChallengeResponse(Challenge, (char *)&PasswordHash, Response); ++ debug_printf(DEBUG_AUTHTYPES, "Response : "); ++ debug_hex_printf(DEBUG_AUTHTYPES, (uint8_t *) Response, 24); ++} ++ ++void GetMasterKey(char *PasswordHashHash, char *NTResponse, char *MasterKey) ++{ ++ EVP_MD_CTX * cntx = EVP_MD_CTX_new(); ++ char Digest[20]; ++ int retLen; ++ ++ char Magic1[27] = ++ {0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, ++ 0x68, 0x65, 0x20, 0x4d, 0x50, 0x50, 0x45, 0x20, 0x4d, ++ 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4b, 0x65, 0x79}; ++ ++ if (cntx == NULL) ++ return; ++ ++ if (!xsup_assert((PasswordHashHash != NULL), "PasswordHashHash != NULL", ++ FALSE)) ++ return; ++ ++ if (!xsup_assert((NTResponse != NULL), "NTResponse != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((MasterKey != NULL), "MasterKey != NULL", FALSE)) ++ return; ++ ++ bzero(&Digest, 20); ++ ++ EVP_DigestInit(cntx, EVP_sha1()); ++ EVP_DigestUpdate(cntx, PasswordHashHash, 16); ++ EVP_DigestUpdate(cntx, NTResponse, 24); ++ EVP_DigestUpdate(cntx, (char *)&Magic1, 27); ++ EVP_DigestFinal(cntx, (uint8_t *)&Digest, (u_int *) &retLen); ++ ++ EVP_MD_CTX_free(cntx); ++ ++ memcpy(MasterKey, &Digest, 16); ++} ++ ++void GetMasterLEAPKey(char *PasswordHashHash, char *APC, char *APR, char *PC, char *PR, char *MasterKey) ++{ ++ EVP_MD_CTX * cntx = EVP_MD_CTX_new(); ++ char Digest[20]; ++ int retLen; ++ ++ if (cntx == NULL) ++ return; ++ ++ if (!xsup_assert((PasswordHashHash != NULL), "PasswordHashHash != NULL", ++ FALSE)) ++ return; ++ ++ if (!xsup_assert((APC != NULL), "APC != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((APR != NULL), "APR != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((PC != NULL), "PC != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((PR != NULL), "PR != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((MasterKey != NULL), "MasterKey != NULL", FALSE)) ++ return; ++ ++ bzero(&Digest, 20); ++ ++ EVP_DigestInit(cntx, EVP_md5()); ++ EVP_DigestUpdate(cntx, PasswordHashHash, 16); ++ EVP_DigestUpdate(cntx, APC, 8); ++ EVP_DigestUpdate(cntx, APR, 24); ++ EVP_DigestUpdate(cntx, PC, 8); ++ EVP_DigestUpdate(cntx, PR, 24); ++ EVP_DigestFinal(cntx, (uint8_t *)&Digest, (u_int *) &retLen); ++ ++ EVP_MD_CTX_free(cntx); ++ ++ memcpy(MasterKey, &Digest, 16); ++ ++} ++ ++void GetAsymetricStartKey(char *MasterKey, char *SessionKey, ++ int SessionKeyLength, int IsSend, int IsServer) ++{ ++ EVP_MD_CTX * cntx = EVP_MD_CTX_new(); ++ char Digest[20]; ++ char Magic[84]; ++ int retLen; ++ ++ char Magic2[84] = ++ {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, ++ 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, ++ 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, ++ 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x6b, 0x65, 0x79, ++ 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, ++ 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65, ++ 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, ++ 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, ++ 0x6b, 0x65, 0x79, 0x2e}; ++ ++ char Magic3[84] = ++ {0x4f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, ++ 0x65, 0x6e, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2c, 0x20, ++ 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, ++ 0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, ++ 0x6b, 0x65, 0x79, 0x3b, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, ++ 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, ++ 0x69, 0x64, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, ++ 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, ++ 0x6b, 0x65, 0x79, 0x2e}; ++ ++ char SHSpad1[40] = ++ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; ++ ++ char SHSpad2[40] = ++ {0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, ++ 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, ++ 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, ++ 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2}; ++ ++ if (cntx == NULL) ++ return; ++ ++ if (!xsup_assert((MasterKey != NULL), "MasterKey != NULL", FALSE)) ++ return; ++ ++ if (!xsup_assert((SessionKey != NULL), "SessionKey != NULL", FALSE)) ++ return; ++ ++ bzero(&Digest, 20); ++ ++ if (IsSend) { ++ if (IsServer) { ++ memcpy(&Magic, &Magic3, 84); ++ } else { ++ memcpy(&Magic, &Magic2, 84); ++ } ++ } else { ++ if (IsServer) { ++ memcpy(&Magic, &Magic2, 84); ++ } else { ++ memcpy(&Magic, &Magic3, 84); ++ } ++ } ++ ++ EVP_DigestInit(cntx, EVP_sha1()); ++ EVP_DigestUpdate(cntx, MasterKey, 16); ++ EVP_DigestUpdate(cntx, SHSpad1, 40); ++ EVP_DigestUpdate(cntx, (char *)&Magic, 84); ++ EVP_DigestUpdate(cntx, SHSpad2, 40); ++ EVP_DigestFinal(cntx, (uint8_t *)&Digest, (u_int *)&retLen); ++ ++ EVP_MD_CTX_free(cntx); ++ ++ memcpy(SessionKey, &Digest, SessionKeyLength); ++} ++ +Index: pam_radius/src/radpeapclient.h +=================================================================== +--- /dev/null ++++ pam_radius/src/radpeapclient.h +@@ -0,0 +1,402 @@ ++/* ++ * Copyright (c) 1996-2007 Brocade Communications Systems, Inc. ++ * All rights reserved. ++ * ++ * Module name: radpeapclient.h ++ * ++ * Description: ++ * This modules deals with PEAP-MSCHAPv2 user authentication ++ * ++ * Portions are of this code are extracted from freeradius-1.1.4, ++ * with modifications for IPv6. ++ * ++ * freeradius-1.1.4 libradius and libeap required for linking. ++ * ++ * Copyright 2019 Broadcom. All rights reserved. ++ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. ++ * ++ */ ++ ++#ifndef _RADPEAPCLIENT_H ++# define _RADPEAPCLIENT_H ++ ++# ifndef ATTR_PACKED ++# define ATTR_PACKED __attribute__ ((__packed__)) ++# endif ++ ++# define COMMAND_SIZE 256 ++# define RESULT_AVP_LEN 2 ++# define RESULT_AVP_RESULT 1 ++# define RAD_NEW_VECTOR 1 ++# define EAP_ID 210 ++# define BROCADE_ID 1588 ++ ++# define TLS_MSG_HEADER_LEN 6 ++# define MAX_CHALLENGE_LEN 16 ++# define MAX_RESPONSE_LEN 24 ++# define MAX_AUTH_RESPONSE_LEN 20 ++ ++# define PW_EAP_MSCHAPV2_RESPONSE 2 ++# define PW_EAP_MSCHAPV2_SUCCESS 3 ++ ++# define PW_EAP_MSCHAPV2 26 ++# define PW_VENDOR_ATTR 26 ++# define PW_EAP_EXTENTIONS 33 ++ ++# define PW_EAP_SECUREID 15 ++# define PAM_AUTH_SECUREID -2 ++ ++# define MSCHAPV2_CHALLENGE_LEN 16 ++# define MSCHAP_RESPONSE_VALUE_SIZE 49 /* RFC demands it to be 49 */ ++ ++# define RESULT_AVP 3 ++# define CLIENT_INIT_STATE 0x0100 ++# define CLIENT_EAP_CONNECT 0x0200 ++# define CLIENT_TLS_HANDSHAKE 0x0400 ++# define CLIENT_TLS_ESTABLISHED 0x0800 ++ ++# define CLIENT_SEND_ACK 0x0001 ++# define CLIENT_PEAP_START 0x0002 ++# define CLIENT_SEND_PEAP_IDENTITY 0x0004 ++# define CLIENT_SEND_MSCHAP_RESPONSE 0x0008 ++# define CLIENT_RECEIVE_AUTH_RESULT 0x0010 ++# define CLIENT_RECEIVE_FINAL_CHALLENGE 0x0020 ++# define CLIENT_TLS_SHUTDOWN 0x0040 ++# define CLIENT_SEND_NAK_RESPONSE 0x0080 ++ ++# define RADIUS_ACCESS_REQUEST 0x1 ++# define RANDOM_ID 0x90 ++# define MORE_TLS_DATA 0x40 ++# define EAP_LENGTH_INCLUDED 0x80 ++ ++# define TLS_HDR_LEN_PLUS_LENGTH 10 /* TLS header length */ ++# define TLS_HDR_LEN 6 ++ /* TLS header length without including the ++ * tls_msg_len field ++ */ ++# define MAX_NT_PASSWORD 256 /* Maximum Password length */ ++# define MAX_NT_USERNAME 256 /* Maximum User name length */ ++# define MAX_SHARED_SEC_LEN 256 /* Maximum shared secret length */ ++ ++#define IP_ADDR_SIZE 4 /* IPV4 -4 bytes */ ++#define IPV6_ADDR_SIZE 16 /* IPV4 -4 bytes */ ++ ++#define MS_RESERVED_BYTES 8 ++ ++# define PEAP_SERVER_RETRY 1 ++ ++#define PAM_AUTHINFO_UNAVAIL 9 ++ ++#define EAP_MESSAGE_MAX 253 ++#define MAX_EAP_PACKETS 26 /* Maximum RADIUS Packet size is 4096 ++ Maximum EAP Packets is 4096/256=16 ++ Keeping buffer of 10 packets */ ++ ++static const char * packet_codes[] = { ++ "", ++ "Access-Request", ++ "Access-Accept", ++ "Access-Reject", ++ "Accounting-Request", ++ "Accounting-Response", ++ "Accounting-Status", ++ "Password-Request", ++ "Password-Accept", ++ "Password-Reject", ++ "Accounting-Message", ++ "Access-Challenge", ++ "Status-Server", ++ "Status-Client", ++ "14", ++ "15", ++ "16", ++ "17", ++ "18", ++ "19", ++ "20", ++ "Resource-Free-Request", ++ "Resource-Free-Response", ++ "Resource-Query-Request", ++ "Resource-Query-Response", ++ "Alternate-Resource-Reclaim-Request", ++ "NAS-Reboot-Request", ++ "NAS-Reboot-Response", ++ "28", ++ "Next-Passcode", ++ "New-Pin", ++ "Terminate-Session", ++ "Password-Expired", ++ "Event-Request", ++ "Event-Response", ++ "35", ++ "36", ++ "37", ++ "38", ++ "39", ++ "Disconnect-Request", ++ "Disconnect-ACK", ++ "Disconnect-NAK", ++ "CoA-Request", ++ "CoA-ACK", ++ "CoA-NAK", ++ "46", ++ "47", ++ "48", ++ "49", ++ "IP-Address-Allocate", ++ "IP-Address-Release" ++}; ++ ++/* ++ * On wire radius packet header ++ * The structure member `data' is used in the program ++ * for copying the lower layer header. ++ */ ++typedef struct radius_packet_t { ++ uint8_t code; ++ uint8_t id; ++ uint16_t length; ++ uint8_t vector[AUTH_VECTOR_LEN]; ++ uint8_t data[0]; ++} ATTR_PACKED radius_packet_t; ++ ++/* ++ * Radius Attribute-Eap Message structure ++ * `data' field is a pointer to the Eap message data portion ++ * Used for copying the real eap message in various packets ++ */ ++typedef struct _eap_message { ++ uint8_t code; ++ uint8_t length; ++ uint8_t data[0]; ++} ATTR_PACKED eap_message_t; ++ ++/* ++ * Eap packet header ++ * Based on the `length' field `type_data' can be of various size ++ * So just a pointer is used to copy over the real data ++ */ ++typedef struct _eap_pkt_t { ++ uint8_t code; ++ uint8_t id; ++ uint16_t length; ++ uint8_t type; ++ uint8_t type_data[0]; ++} ATTR_PACKED eap_pkt_t; ++ ++/* ++ * General TLV format, `data' can be of any length ++ * a pointer is used to carry out copiying, ++ * its not a actual structure member ++ */ ++typedef struct _type_length_value { ++ uint8_t type; ++ uint8_t length; ++ uint8_t data[0]; ++} ATTR_PACKED type_length_value_t; ++ ++/* ++ * Type-Value pair, used to represent EAP message ++ * where there is no `length' field as compared to TLV ++ */ ++typedef struct _type_value { ++ uint8_t type; ++ uint8_t data[0]; ++} ATTR_PACKED type_value_t; ++ ++/* ++ * TLS layer header, used for communicating to and from ++ * SSL library functions ++ */ ++typedef struct _eap_tls_response { ++ uint8_t code; ++ uint8_t id; ++ uint16_t length; ++ uint8_t type; ++ uint8_t flags; ++ uint32_t tls_msg_len; ++ uint8_t data[0]; ++} ATTR_PACKED eap_tls_response_t; ++ ++/* ++ * EAP TLS acknowledgement packet format ++ */ ++typedef struct _eap_tls_ack_t { ++ uint8_t code; ++ uint8_t id; ++ uint16_t length; ++ uint8_t type; ++ uint8_t flags; ++} ATTR_PACKED eap_tls_ack_t; ++ ++/* ++ * SSL protocol version structure, RFC 2246 ++ */ ++typedef struct _proto_ver { ++ uint8_t major; ++ uint8_t minor; ++} ATTR_PACKED protocol_version_t; ++ ++/* ++ * SSL content Type enums, content type specifies what type of ++ * data SSL packet is containing, RFC 2246 ++ */ ++typedef enum { ++ CHANGE_CIPHER_SPEC = 20, ++ ALERT, ++ HANDSHAKE, ++ APPLICATION_DATA ++} content_type ; ++ ++ ++/* ++ * TLS record layer protocol header, RFC 2246 ++ */ ++typedef struct TLS_RECORD_LAYER { ++ content_type proto_type; ++ protocol_version_t version; ++ uint16_t length; ++ uint8_t fragment[1]; ++} ATTR_PACKED tls_record_layer_t; ++ ++/* ++ * MSCHAP V2 response header ++ * ietf draft Kamath & Palekar ++ */ ++typedef struct _mschapv2_response_packet_t { ++ uint8_t opcode; ++ uint8_t mschapv2_id; ++ uint8_t ms_length[2]; ++ uint8_t value_size; ++ uint8_t peer_challenge[16]; ++ uint8_t reserved[8]; ++ uint8_t nt_response[24]; ++ uint8_t flags; ++ uint8_t data[1]; ++} ATTR_PACKED mschapv2_response_packet_t; ++ ++/* ++ * MSCHAP V2 request header ++ * ietf draft Kamath & Palekar ++ */ ++typedef struct _mschapv2_request_packet_t { ++ uint8_t opcode; ++ uint8_t mschapv2_id; ++ uint8_t ms_length[2]; ++ uint8_t value_size; ++ uint8_t auth_challenge[16]; ++ uint8_t name[1]; ++} ATTR_PACKED mschapv2_request_packet_t; ++ ++/* ++ * Used for maintaining reciened TLS fragments in linked list format, ++ * untill we get all the fragments which is needed for reassembly ++ * Once all fragments are recieved, free the list ++ */ ++typedef struct _tls_data_list { ++ uint8_t * tls_data; ++ int tls_data_length; ++ struct _tls_data_list * next; ++ struct _tls_data_list * list_end; ++} tls_data_list_t; ++ ++/* ++ * The global data structure used for maintaining *all* the ++ * information needed at any point of time during the ++ * execution of the program ++ */ ++typedef struct _peap_instance { ++#if 0 ++ uint32_t dst_ipaddr; ++ u_short dst_port; ++#endif ++ struct sockaddr *dst_addr; ++ struct sockaddr *src_addr; ++ int sockfd; ++ char * shared_secret; ++ char * user_name; ++ char * user_passwd; ++ tls_session_t *tls_session; ++ RADIUS_PACKET *prev_rad_pkt; ++ RADIUS_PACKET *current_rad_pkt; ++ RADIUS_PACKET *server_rep; ++ uint8_t * srv_eap_message; ++ tls_data_list_t srv_tls_data_list; ++ uint16_t client_state; ++ uint8_t ssl_state; ++ void *cur_serv; ++ int retries; ++} peap_instance_t; ++ ++/* ++ * Following (radius_server_t, and radius_conf_t) ++ * taken from pam_radius_auth.h because there are conflicting ++ * declarations in header files in freeradius and pam_radius ++ * [ *** Please make sure they are in sync. **** ] ++ */ ++ ++#if defined(HAVE_LINUX_IF_H) ++#include ++#else ++#define IFNAMSIZ 16 /* fallback to current value */ ++#endif ++ ++#ifndef CONST ++# 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 ++ */ ++# define CONST const ++# else ++# define CONST ++# endif ++#endif ++ ++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; ++#define MAXPROMPT 33 /* max prompt length, including '\0' */ ++ char prompt[MAXPROMPT]; ++ int prompt_attribute; ++ int privilege_level; ++ int auth_type; ++} radius_conf_t; ++ ++/* ++ * Following taken from radius.h because there are conflicting ++ * declarations in header files in freeradius and pam_radius ++ * [ *** Please make sure they are in sync. **** ] ++ */ ++typedef struct pw_auth_hdr { ++ uint8_t code; ++ uint8_t id; ++ uint16_t length; ++ uint8_t vector[AUTH_VECTOR_LEN]; ++ uint8_t data[2]; ++} AUTH_HDR; ++ ++#endif ++ +Index: pam_radius/src/radpeapclient.c +=================================================================== +--- /dev/null ++++ pam_radius/src/radpeapclient.c +@@ -0,0 +1,3346 @@ ++/* ++ * Copyright (c) 1996-2007 Brocade Communications Systems, Inc. ++ * All rights reserved. ++ * ++ * Module name: radpeapclient.c ++ * ++ * Description: ++ * This modules does with PEAP-MSCHAPv2 user authentication ++ * ++ * Portions are of this code are extracted from freeradius-1.1.4, ++ * with modifications for IPv6. ++ * ++ * freeradius-1.1.4 libradius and libeap required for linking. ++ * ++ * Copyright 2019 Broadcom. All rights reserved. ++ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include "eap_tls.h" ++#include "conf.h" ++#include "radpaths.h" ++#include "missing.h" ++#include "md5.h" ++#include "eap_types.h" ++#include "eap_sim.h" ++#include "radpeapclient.h" ++// #include "mschapv2.h" ++// #include "pam_radius_auth.h" ++ ++static unsigned char cached_auth_challenge[16] = { 0 }; ++static unsigned char cached_peer_challenge[16] = { 0 }; ++static unsigned char cached_nt_response[24] = { 0 }; ++ ++/* All these globals (including peap_instance) implies, that we ++ * are *not* MT-safe. ++ */ ++static peap_instance_t peap_instance; ++static const char * xlookuptbl = {"0123456789ABCDEF"}; ++const char * radius_dir = RADDBDIR; ++int debug_flag = 0; ++ ++/* ++ * uint8_t *compose_nak_response ++ * ++ * Description ++ * Function builds nak response packet for unknow request ++ * send by the server based on the state machine's state ++ * ++ * Input ++ * pointer for NAK data length ++ * ++ * Output ++ * Composed NAK response packets's length ++ * ++ * Return ++ * Returns the inner tlv NAK data ++ */ ++static uint8_t * ++compose_nak_respone(int *nak_data_len) ++{ ++ uint8_t * ptr = 0; ++ VALUE_PAIR * first_vp = 0; ++ RADIUS_PACKET * rep = 0; ++ ++ /* Get the latest server reply */ ++ rep = peap_instance.server_rep; ++ if (0 == rep) { ++ peap_log_debug("compose_nak_response : server rep is NULL!!!"); ++ return 0; ++ } ++ /* Extract the EAP Message from the AVP's */ ++ first_vp = pairfind(rep->vps, PW_EAP_MESSAGE); ++ if (0 == first_vp) { ++ peap_log_debug("compose_nak_response : %d: first_vp is NULL \n", ++ __LINE__); ++ return 0; ++ } ++ ++ *nak_data_len = 1 /* PW_EAP_NAK */ + 1 /* PW_EAP_MSCHAPV2 */; ++ ++ ptr = (uint8_t *)calloc(*nak_data_len, sizeof(uint8_t)); ++ if (0 == ptr) { ++ peap_log_debug("compose_nak_response: Memory allocation failed "); ++ return 0; ++ } ++ *ptr = PW_EAP_NAK; /* EAP-Type */ ++ *(ptr + 1) = PW_EAP_MSCHAPV2; /* Authentication Type */ ++ ++ return ptr; /* Return from here itself, don't fall through */ ++} ++ ++/* ++ * int radlog ++ * ++ * Description ++ * Function used for logging by freeradius ++ * ++ * Input ++ * Var Args ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++int ++radlog(int lvl, const char *msg, ...) ++{ ++ va_list ap; ++ int r; ++ r = lvl; /* shut up compiler */ ++ va_start(ap, msg); ++ r = vfprintf(stderr, msg, ap); ++ va_end(ap); ++ fputc('\n', stderr); ++ return r; ++} ++ ++ ++/* From pam_radius_auth.c =========== ++ */ ++/* internal data */ ++static CONST char *pam_module_name = "pam_radius_auth"; ++ ++/* logging */ ++static void peap_log(int err, CONST char *format, ...) ++{ ++ va_list args; ++ char buffer[BUFFER_SIZE]; ++ ++ va_start(args, format); ++ 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); ++} ++ ++/* End From pam_radius_auth.c =========== ++ */ ++ ++ ++/* ++ * int peap_log_debug ++ * ++ * Description ++ * Function used for logging ++ * ++ * Input ++ * Var Args ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++int ++peap_log_debug(const char * msg, ...) ++{ ++#if 0 ++ Rewritten like peap_log() ++ FILE * fp; ++ va_list ap; ++ int r; ++#define FILENAME "/var/log/peaplog" ++ ++ fp = fopen(FILENAME, "a"); ++ if(0 == fp) { ++ return -1; ++ } ++ va_start(ap, msg); ++ r = vfprintf(fp, msg, ap); ++ va_end(ap); ++ fputc('\n', fp); ++ fclose(fp); ++ ++ return r; ++#else ++ va_list args; ++ char buffer[BUFFER_SIZE]; ++ int r; ++ ++ va_start(args, msg); ++ r = vsnprintf(buffer, sizeof(buffer), msg, args); ++ /* don't do openlog or closelog, but put our name in to be friendly */ ++ syslog(LOG_DEBUG, "%s: %s", pam_module_name, buffer); ++ va_end(args); ++ ++ return r; ++#endif ++} ++ ++/* ++ * static void encode_asc ++ * ++ * Description ++ * Convert a hexadecimal value into corresponding ascii value ++ * ++ * Input ++ * unsigned int num ++ * ++ * Output ++ * char * encoded ++ * ++ * Return ++ * None ++ */ ++static void ++encode_asc(uint8_t num, char * encoded) ++{ ++ encoded[0] = xlookuptbl[(num & 0xF0) >> 4]; ++ encoded[1] = xlookuptbl[(num & 0x0F)]; ++} ++ ++/* ++ * int compose_eap_identity_pkt ++ * ++ * Description ++ * Compose the first radius packet from the client ++ * ++ * Input ++ * RADIUS_PACKET * req ++ * ++ * Output ++ * RADIUS_PACKET * req ++ * ++ * Return ++ * Success or failure ++ */ ++static int ++compose_eap_identity_pkt(RADIUS_PACKET * req) ++{ ++ char mesg_auth[AUTH_VECTOR_LEN] = { 0 }; ++ char eap_identity_str[MAX_NT_USERNAME] = { 0 }; ++ uint16_t eap_pkt_size = 0; ++ uint16_t eap_msg_size = 0; ++ uint16_t rad_pkt_size = 0; ++ uint16_t tv_size = 0; ++ uint16_t temp_len = 0 ; ++ uint32_t nas_ip_addr = 0; ++ uint8_t * nas_ipv6_addr = NULL; ++ int user_tlv_size = 0; ++ int nas_tlv_size = 0; ++ int port_tlv_size = 0; ++ uint8_t * ptr = 0; ++ eap_message_t * eap_msg = 0; ++ eap_pkt_t * eap_pkt = 0; ++ radius_packet_t * rad_pkt = 0; ++ type_length_value_t * tv = 0; ++ type_length_value_t * user_tlv = 0; ++ type_length_value_t * nas_tlv = 0; ++ type_length_value_t * port_tlv = 0; ++ ++#undef CLEAN_UP ++#define CLEAN_UP() \ ++ do { \ ++ if(eap_pkt) { free(eap_pkt); eap_pkt = 0; } \ ++ if(eap_msg) { free(eap_msg); eap_msg = 0; } \ ++ if(tv) { free(tv); tv = 0; } \ ++ if(user_tlv) { free(user_tlv); user_tlv = 0; } \ ++ if(nas_tlv) { free(nas_tlv); nas_tlv = 0; } \ ++ if(port_tlv) { free(port_tlv); port_tlv = 0; } \ ++ } while (0); ++ ++ strncpy(eap_identity_str, peap_instance.user_name , strlen(peap_instance.user_name)); ++ ++ eap_pkt_size = sizeof(eap_pkt_t) + strlen(eap_identity_str); ++ eap_pkt = calloc(eap_pkt_size, sizeof(uint8_t)); ++ if (0 == eap_pkt) { ++ peap_log_debug("compose_eap_identity_pkt: Memory allocation failed,eap_pkt is NULL\n "); ++ return -1; ++ } ++ eap_pkt->code = PW_EAP_RESPONSE; ++ eap_pkt->id = EAP_ID; /* Some Random ID it is */ ++ ++ temp_len = htons(eap_pkt_size); ++ eap_pkt->length = temp_len; ++ eap_pkt->type = PW_EAP_IDENTITY; ++ memcpy(eap_pkt->type_data, eap_identity_str, strlen(eap_identity_str)); ++ ++ eap_msg_size = sizeof(eap_message_t) + eap_pkt_size; ++ ++ /* ++ * Allocate memory for EAP Message Radius Attribute ++ */ ++ eap_msg = calloc(eap_msg_size, sizeof(uint8_t)); ++ if(0 == eap_msg_size) { ++ peap_log_debug("compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ eap_msg->code = PW_EAP_MESSAGE; ++ memcpy(eap_msg->data, (eap_packet_t *) eap_pkt, eap_pkt_size); ++ eap_msg->length = eap_msg_size; ++ tv_size = sizeof(type_length_value_t) + AUTH_VECTOR_LEN; ++ tv = calloc(tv_size, sizeof(uint8_t)); ++ if(0 == tv) { ++ peap_log_debug("compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ tv->type = PW_MESSAGE_AUTHENTICATOR; ++ tv->length = tv_size; ++ /* ++ * User TLV ,Contains the user credentials initailly only user_name ++ */ ++ user_tlv_size = sizeof(type_length_value_t) + strlen(eap_identity_str); ++ user_tlv = calloc(user_tlv_size, sizeof(uint8_t)); ++ if(0 == user_tlv) { ++ peap_log_debug("compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ user_tlv->type = PW_USER_NAME; ++ user_tlv->length = user_tlv_size; ++ memcpy(user_tlv->data, eap_identity_str, strlen(eap_identity_str)); ++ ++ /* ++ * NAS IP address, Remote Access policy dictates this to be present ++ */ ++ switch ((peap_instance.dst_addr)->sa_family) { ++ ++ case AF_INET: ++ ++ /* 4 bytes of IPV4 address ++ */ ++ nas_tlv_size = sizeof(type_length_value_t) + IP_ADDR_SIZE; ++ nas_tlv = calloc(nas_tlv_size, sizeof(uint8_t)); ++ if( 0 == nas_tlv ) { ++ peap_log_debug( ++ "compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ nas_tlv->type = PW_NAS_IP_ADDRESS; ++ nas_tlv->length = nas_tlv_size; ++ nas_ip_addr = ((struct sockaddr_in *)peap_instance.dst_addr)->sin_addr.s_addr; ++ memcpy(nas_tlv->data, (uint32_t *) & nas_ip_addr, sizeof(uint32_t)); ++ break; ++ ++ case AF_INET6: ++ ++ /* 16 bytes of IPV6 address ++ */ ++ nas_tlv_size = sizeof(type_length_value_t) + IPV6_ADDR_SIZE; ++ nas_tlv = calloc(nas_tlv_size, sizeof(uint8_t)); ++ if( 0 == nas_tlv ) { ++ peap_log_debug( ++ "compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ nas_tlv->type = PW_NAS_IPV6_ADDRESS; ++ nas_tlv->length = nas_tlv_size; ++ nas_ipv6_addr = &(((struct sockaddr_in6 *)peap_instance.dst_addr)->sin6_addr.s6_addr); ++ memcpy(nas_tlv->data, nas_ipv6_addr, IPV6_ADDR_SIZE); ++ break; ++ ++ default: ++ nas_tlv_size = 0; ++ nas_tlv = NULL; ++ break; ++ } ++ ++ /* ++ * NAS port tlv, value can be zero also ++ */ ++ port_tlv_size = sizeof(type_length_value_t) + sizeof(uint32_t); ++ port_tlv = calloc(port_tlv_size, sizeof(uint8_t)); ++ if(0 == port_tlv) { ++ peap_log_debug("compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ port_tlv->type = PW_NAS_PORT; ++ port_tlv->length = port_tlv_size; ++ memset(port_tlv->data, 0, sizeof(uint32_t)); ++ ++ rad_pkt_size = sizeof(radius_packet_t) + eap_msg_size + tv_size + ++ user_tlv_size + nas_tlv_size + port_tlv_size; ++ ++ rad_pkt = calloc(rad_pkt_size, sizeof(uint8_t)); ++ if(0 == rad_pkt) { ++ peap_log_debug("compose_eap_identity_pkt:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ rad_pkt->code = RADIUS_ACCESS_REQUEST; /* Radius Access-Request */ ++ rad_pkt->id = RANDOM_ID; /* Random ID to start with */ ++ memset(rad_pkt->vector, 0, AUTH_VECTOR_LEN); ++ ++ ptr = (uint8_t *)rad_pkt->data; /* ++ * Now we are going to pack all these tlv's into a packet ++ * We gonna play with these temporarry variable `ptr' ++ */ ++ memcpy(ptr, user_tlv, user_tlv_size); ++ ptr += user_tlv_size; /* ++ * These tlv's can be any order, ++ * let user_tlv get packed first ++ */ ++ if (nas_tlv) { ++ memcpy(ptr, nas_tlv, nas_tlv_size); ++ ptr += nas_tlv_size; /* pack NAS port tlv, advance the pointer accordingly */ ++ } ++ ++ memcpy(ptr, port_tlv, port_tlv_size); ++ ptr += port_tlv_size; /* PORT tlv , does not seems to be that much useful */ ++ ++ memcpy(ptr, eap_msg, eap_msg_size); /* Here comes the real EAP Message */ ++ ++ ptr += eap_msg_size; ++ memcpy(ptr, tv, tv_size); ++ /* Protect the entire radiius packet with ++ * the message authenticator ++ */ ++ ++ temp_len = htons(rad_pkt_size); ++ rad_pkt->length = temp_len; ++ ++ /* Calculate the radius paket vector */ ++ librad_md5_calc(rad_pkt->vector, rad_pkt->vector, sizeof(rad_pkt->vector)); ++ ++ lrad_hmac_md5((uint8_t *) rad_pkt, rad_pkt_size, peap_instance.shared_secret, ++ strlen(peap_instance.shared_secret), mesg_auth); ++ ++ ptr += 2 ; /* Skip the length field */ ++ memcpy(ptr, mesg_auth, AUTH_VECTOR_LEN); ++ req->data = (uint8_t *)rad_pkt; ++ req->data_len = rad_pkt_size; ++ CLEAN_UP(); ++ return 0; ++} ++ ++/* ++ * static void record_peap_init ++ * ++ * Description ++ * TLS session initialisation function ++ * ++ * Input ++ * record_t * rec ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++static void ++record_peap_init(record_t * rec) ++{ ++ rec->used = 0; ++} ++ ++/* ++ * static void record_peap_close ++ * ++ * Description ++ * TLS session Termination function ++ * ++ * Input ++ * record_t * rec ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++static void ++record_peap_close(record_t * rec) ++{ ++ rec->used = 0; ++} ++ ++/* ++ * static unsigned int record_peap_plus ++ * ++ * Description ++ * Copy data to the intermediate buffer, before we send it somewhere ++ * ++ * Input ++ * record_t * rec, const void * ptr, unsigned int size ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Total no of bytes added to the buffer ++ */ ++static unsigned int ++record_peap_plus(record_t * rec, const void * ptr, unsigned int size) ++{ ++ unsigned int added = MAX_RECORD_SIZE - rec->used; ++ if (added > size) ++ added = size; ++ if (added == 0) ++ return 0; ++ memcpy(rec->data + rec->used, ptr, added); ++ rec->used += added; ++ return added; ++} ++ ++/* ++ * static unsigned int record_peap_minus ++ * ++ * Description ++ * Take data from the buffer, and give it to the caller ++ * ++ * Input ++ * record_t * rec ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Total no of bytes taken out from the buffer ++ */ ++static unsigned int ++record_peap_minus(record_t * rec, void * ptr, unsigned int size) ++{ ++ unsigned int taken = rec->used; ++ if (taken > size) ++ taken = size; ++ if (taken == 0) ++ return 0; ++ if (ptr) ++ memcpy(ptr, rec->data, taken); ++ rec->used -= taken; ++ /* ++ * This is pretty bad... ++ */ ++ if (rec->used > 0) ++ memmove(rec->data, rec->data + taken, rec->used); ++ return taken; ++} ++ ++/* ++ * static void cb_peap_info ++ * ++ * Description ++ * Info call back function for SLL functions ++ * ++ * Input ++ * const SSL * s, int where ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++static void ++cb_peap_info(const SSL * s, int where, int unused) ++{ ++ const char * str, * state; ++ int w; ++ w = where & ~SSL_ST_MASK; ++ if (w & SSL_ST_CONNECT) ++ str = "TLS_connect"; ++ else if (w & SSL_ST_ACCEPT) ++ str = "TLS_accept"; ++ else ++ str = "(other)"; ++ ++ state = SSL_state_string_long(s); ++ state = state ? state : "NULL"; ++ ++ return; ++} ++ ++/* ++ * void cb_peap_msg ++ * ++ * Description ++ * Message call back function for SSL. ++ * Function get invoked from SSL layer , when a SSL handshake ++ * happens ,so that we can know what messages are exchaned ++ * ++ * Input ++ * int write_p, int msg_version, int content_type, ++ * const void * buf, size_t len, void * arg ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++static void ++cb_peap_msg(int write_p, int msg_version, int content_type, ++ const void * buf, size_t len, SSL * ssl UNUSED, void * arg) ++{ ++ tls_session_t * state = (tls_session_t *) arg; ++ ++ /* ++ * Work around bug #298, where we may be called with a NULL ++ * argument. We should really log a serious error ++ */ ++ if (!arg) ++ return; ++ ++ state->info.origin = (unsigned char) write_p; ++ state->info.content_type = (unsigned char) content_type; ++ state->info.record_len = len; ++ state->info.version = msg_version; ++ state->info.initialized = 1; ++ ++ if (content_type == SSL3_RT_ALERT) { ++ state->info.alert_level = ((const unsigned char *) buf)[0]; ++ state->info.alert_description = ((const unsigned char *) buf)[1]; ++ state->info.handshake_type = 0x00; ++ ++ } else if (content_type == SSL3_RT_HANDSHAKE) { ++ state->info.handshake_type = ((const unsigned char *) buf)[0]; ++ state->info.alert_level = 0x00; ++ state->info.alert_description = 0x00; ++ } ++ tls_session_information(state); ++} ++ ++/* ++ *RADIUS_PACKET * compose_eap_packet ++ * ++ * Description ++ * This function composes the radius packets on-wire which contains ++ * TLVs like eap message , nas ip address , nas port , ++ * state , message authenticator etc . ++ * ++ * Input ++ * RADIUS_PACKET * req , char *data, ++ * int len , uint8_t include_length . ++ * ++ * Output ++ * Radius packet on-wire ++ * ++ * Return ++ * RADIUS_PACKET * req ++ */ ++static RADIUS_PACKET * ++compose_eap_packet(RADIUS_PACKET * req, char * data, int len, uint8_t include_length) ++{ ++ char eap_identity_str[MAX_NT_USERNAME] = { 0 }; ++ char mesg_auth[AUTH_VECTOR_LEN] = { 0 }; ++ uint16_t eap_pkt_size = 0; ++ uint16_t ep_pkt_size = 0; ++ uint16_t eap_msg_size[MAX_EAP_PACKETS] = { 0 }; ++ uint16_t rad_pkt_size = 0; ++ uint16_t tls_msg_length = 0; ++ uint16_t tv_size = 0; ++ uint16_t tls_record_length = 0; ++ uint16_t state_tv_size = 0; ++ uint16_t temp_len = 0 ; ++ uint32_t temp_len_l = 0 ; ++ uint32_t nas_ip_addr = 0; ++ uint8_t * nas_ipv6_addr = 0; ++ int user_tlv_size = 0; ++ int nas_tlv_size = 0; ++ int port_tlv_size = 0; ++ uint8_t * srv_eap_message = 0; ++ uint8_t * ptr = 0; ++ tls_record_layer_t * tls_record = 0; ++ type_length_value_t * tv = 0; ++ type_length_value_t * state_tv = 0; ++ type_length_value_t * user_tlv = 0; ++ type_length_value_t * nas_tlv = 0; ++ type_length_value_t * port_tlv = 0; ++ VALUE_PAIR * eap_state = 0; ++ RADIUS_PACKET * rep = 0; ++ radius_packet_t * rad_pkt = 0; ++ eap_tls_response_t * eap_pkt = 0; ++ eap_tls_response_t * eap_pktptr = 0; ++ eap_message_t * eap_msg_arr[MAX_EAP_PACKETS] = { 0 }; ++ int no_eap_message = 0; ++ int tmpcount = 0; ++ ++#undef CLEAN_UP ++#define CLEAN_UP() \ ++ do { \ ++ if(user_tlv) { free(user_tlv); user_tlv = 0; } \ ++ if(nas_tlv) { free(nas_tlv);nas_tlv = 0; } \ ++ if(port_tlv) { free(port_tlv); port_tlv = 0; } \ ++ if(tls_record) { free(tls_record); tls_record = 0; } \ ++ if(eap_pkt) { free(eap_pkt); eap_pkt = 0; } \ ++ if(state_tv) { free(state_tv); state_tv = 0; } \ ++ if(tv) { free(tv); tv = 0; } \ ++ for(tmpcount = 0; tmpcount < MAX_EAP_PACKETS; tmpcount++) { \ ++ if(eap_msg_arr[tmpcount]) { \ ++ free(eap_msg_arr[tmpcount]); \ ++ } \ ++ } \ ++ } while (0); ++ ++ strncpy(eap_identity_str, peap_instance.user_name, strlen(peap_instance.user_name)); ++ ++ tls_record_length = len; ++ if (0 != len) { ++ eap_pkt_size = sizeof(eap_tls_response_t) + len; ++ if (FALSE == include_length) { /* Check Length Included flag is set or not */ ++ eap_pkt_size -= sizeof(uint32_t); /* Jump by tls_msg_length size */ ++ } ++ } else { ++ eap_pkt_size = sizeof(eap_pkt_t ) + sizeof(uint8_t); ++ } ++ ++ tv_size = sizeof(type_length_value_t) + AUTH_VECTOR_LEN; ++ tls_msg_length = sizeof(eap_tls_response_t) + len; ++ ++ /* ++ * User TLV, in each packet it all these tlv's are needed . ++ */ ++ user_tlv_size = sizeof(type_length_value_t) + strlen(eap_identity_str); ++ user_tlv = calloc(user_tlv_size, sizeof(uint8_t)); ++ if(0 == user_tlv) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, user_tlv is NULL \n"); ++ return 0; ++ } ++ user_tlv->type = PW_USER_NAME; /* Pack the user name as a tlv */ ++ user_tlv->length = user_tlv_size; ++ memcpy(user_tlv->data, eap_identity_str, strlen(eap_identity_str)); ++ ++ /* ++ * NAS IP address, it is need in all packets as ++ * W2K3 server Remote Access policy is based on this ++ */ ++ switch ((peap_instance.dst_addr)->sa_family) { ++ ++ case AF_INET: ++ ++ /* 4 bytes of IPV4 address ++ */ ++ nas_tlv_size = sizeof(type_length_value_t) + IP_ADDR_SIZE; ++ nas_tlv = calloc(nas_tlv_size, sizeof(uint8_t)); ++ if( 0 == nas_tlv ) { ++ peap_log_debug( ++ "compose_eap_packet:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ nas_tlv->type = PW_NAS_IP_ADDRESS; ++ nas_tlv->length = nas_tlv_size; ++ nas_ip_addr = ((struct sockaddr_in *)peap_instance.dst_addr)->sin_addr.s_addr; ++ memcpy(nas_tlv->data, (uint32_t *) & nas_ip_addr, sizeof(uint32_t)); ++ break; ++ ++ case AF_INET6: ++ ++ /* 16 bytes of IPV6 address ++ */ ++ nas_tlv_size = sizeof(type_length_value_t) + IPV6_ADDR_SIZE; ++ nas_tlv = calloc(nas_tlv_size, sizeof(uint8_t)); ++ if( 0 == nas_tlv ) { ++ peap_log_debug( ++ "compose_eap_packet:Memory allocation failed,eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return -1; ++ } ++ nas_tlv->type = PW_NAS_IPV6_ADDRESS; ++ nas_tlv->length = nas_tlv_size; ++ nas_ipv6_addr = &(((struct sockaddr_in6 *)peap_instance.dst_addr)->sin6_addr.s6_addr); ++ memcpy(nas_tlv->data, nas_ipv6_addr, IPV6_ADDR_SIZE); ++ break; ++ ++ default: ++ nas_tlv_size = 0; ++ nas_tlv = NULL; ++ break; ++ } ++ ++ /* ++ * Port TLV ++ */ ++ port_tlv_size = sizeof(type_length_value_t) + sizeof(uint32_t); ++ port_tlv = calloc(port_tlv_size, sizeof(uint8_t)); ++ if(0 == port_tlv) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, port_tlv is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ port_tlv->type = PW_NAS_PORT; ++ port_tlv->length = port_tlv_size; ++ memset(port_tlv->data, 0, sizeof(uint32_t)); ++ ++ tls_record = calloc(tls_record_length, sizeof(uint8_t)); ++ if(0 == tls_record) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, tls_record is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ memcpy(tls_record, data, tls_record_length); ++ /* ++ * Extract PW_EAP_MESSAGE ++ */ ++ rep = peap_instance.server_rep; ++ ++ srv_eap_message = peap_instance.srv_eap_message; ++ if (0 == srv_eap_message) { ++// peap_log_debug("compose_eap_packet: %d: srv_eap_message is NULL \n", __LINE__); ++ return 0; ++ } ++ eap_pkt = calloc(eap_pkt_size, sizeof(uint8_t)); ++ if(0 == eap_pkt) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, eap_pkt is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ eap_pkt->code = PW_EAP_RESPONSE; ++ eap_pkt->id = (uint8_t)*(srv_eap_message + 1); ++ temp_len = htons(eap_pkt_size); ++ eap_pkt->length = temp_len; ++ eap_pkt->type = PW_EAP_PEAP; ++ ++ if (0 != len) { ++ if (TRUE == include_length) { ++ eap_pkt->flags = EAP_LENGTH_INCLUDED; ++ // Length should be EAP message's length. Full EAP packet length should not be added ++ temp_len_l = htonl(len); ++ eap_pkt->tls_msg_len = temp_len_l; ++ memcpy(eap_pkt->data, tls_record, tls_record_length); ++ } else { ++ eap_pkt->flags = 0x0; ++ memcpy(&(eap_pkt->tls_msg_len), tls_record, tls_record_length); ++ } ++ } else { ++ eap_pkt->flags = 0x0; ++ eap_pkt_size = TLS_MSG_HEADER_LEN; ++ } ++ ++ /* ++ * Maximum EAP message data can only be 253. For TLS data above 253 ++ * data has to be fragmented and sent with multiple EAP-Message att ++ */ ++ ++ eap_pktptr = eap_pkt; ++ do { ++ if (eap_pkt_size > EAP_MESSAGE_MAX){ ++ ep_pkt_size = EAP_MESSAGE_MAX; ++ } else { ++ ep_pkt_size = eap_pkt_size; ++ } ++ eap_msg_size[no_eap_message] = sizeof(eap_message_t) + ep_pkt_size; ++ eap_msg_arr[no_eap_message] = calloc(eap_msg_size[no_eap_message], sizeof(uint8_t)); ++ if(0 == eap_msg_arr[no_eap_message]) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, eap_msg is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ eap_msg_arr[no_eap_message]->code = PW_EAP_MESSAGE; ++ memcpy(eap_msg_arr[no_eap_message]->data, (char *) eap_pktptr , ep_pkt_size); ++ ++ if (eap_pkt_size > EAP_MESSAGE_MAX) { ++ eap_pktptr = (char *)eap_pktptr + ep_pkt_size; ++ } ++ ++ if (eap_pkt_size > EAP_MESSAGE_MAX) { ++ eap_pkt_size = eap_pkt_size - EAP_MESSAGE_MAX; ++ } else { ++ eap_pkt_size = 0; ++ } ++ ++ eap_msg_arr[no_eap_message]->length = eap_msg_size[no_eap_message]; ++ ++ ++ no_eap_message++; ++ } while ( eap_pkt_size > 0 ); ++ ++ ++ eap_state = pairfind(rep->vps, PW_STATE); ++ if (0 == eap_state) { ++ peap_log_debug("compose_eap_packet: Error while getting PW_STATE \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ state_tv_size = sizeof(type_length_value_t) + eap_state->length; ++ state_tv = calloc(state_tv_size, sizeof(uint8_t)); ++ if(0 == state_tv) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, state_tv is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ state_tv->type = PW_STATE; ++ state_tv->length = state_tv_size; ++ memcpy(state_tv->data, eap_state->strvalue, eap_state->length); ++ tv_size = sizeof(type_length_value_t) + AUTH_VECTOR_LEN; ++ tv = calloc(tv_size, sizeof(uint8_t)); ++ if(0 == tv) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, tv is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ tv->type = PW_MESSAGE_AUTHENTICATOR; ++ tv->length = tv_size; ++ ++ rad_pkt_size = sizeof(radius_packet_t) + state_tv_size + ++ tv_size + user_tlv_size + nas_tlv_size + port_tlv_size; ++ ++ int no_eap_msg = no_eap_message; ++ for (tmpcount = 0; no_eap_msg > 0; no_eap_msg-- ) { ++ rad_pkt_size = rad_pkt_size + eap_msg_size[tmpcount]; ++ tmpcount++; ++ } ++ ++ rad_pkt = calloc(rad_pkt_size, sizeof(uint8_t)); ++ if(0 == rad_pkt) { ++ peap_log_debug("compose_eap_packet:Memory alocation failed, nas_tlv is NULL \n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ rad_pkt->code = RADIUS_ACCESS_REQUEST; /* Radius Access-Request */ ++ rad_pkt->id = rep->id + 1; /* Each EAP request should have unique ID*/ ++ memset(rad_pkt->vector, 0, AUTH_VECTOR_LEN); ++ ptr = (uint8_t *) rad_pkt->data; ++ temp_len = htons(rad_pkt_size); ++ rad_pkt->length = temp_len; ++ ++ /* Calculate the vector digest and put into the radius packet */ ++ librad_md5_calc(req->vector, rep->vector, sizeof(rep->vector)); ++ memcpy(rad_pkt->vector, req->vector, sizeof(rad_pkt->vector)); ++ ++ /* Pack the packet by copying all tlv's */ ++ memcpy(ptr, user_tlv, user_tlv_size); ++ ptr += user_tlv_size; ++ ++ if (nas_tlv) { ++ memcpy(ptr, nas_tlv, nas_tlv_size); ++ ptr += nas_tlv_size; ++ } ++ ++ memcpy(ptr, port_tlv, port_tlv_size); ++ ptr += port_tlv_size; ++ ++ memcpy(ptr, state_tv, state_tv_size); ++ ptr += state_tv_size; ++ ++ tmpcount = 0; ++ do { ++ memcpy(ptr, eap_msg_arr[tmpcount], eap_msg_size[tmpcount]); ++ ptr = ptr + eap_msg_size[tmpcount]; ++ no_eap_message--; ++ tmpcount++; ++ } while(no_eap_message > 0); ++ memcpy(ptr, tv, tv_size); ++ ++ lrad_hmac_md5((uint8_t *) rad_pkt, rad_pkt_size, peap_instance.shared_secret, ++ strlen(peap_instance.shared_secret), mesg_auth); ++ ptr += 2; /* Jump eap message length */ ++ memcpy(ptr, mesg_auth, AUTH_VECTOR_LEN); ++ ++ req->data = (uint8_t *) rad_pkt; ++ req->data_len = rad_pkt_size; ++ CLEAN_UP(); ++ return req; ++} ++ ++/* ++ * int radpeap_verifyCipher() ++ * ++ * Description: ++ * This function obtains the cipher configured for RADIUS from the ++ * config file. ++ * ++ * Input (s): ++ * Pointer to fill the cipher ++ * ++ * Output (s): ++ * Populate the input with the configured cipher ++ * ++ * Returns: ++ * 0 False; 1 True; ++ */ ++int ++radpeap_verifyCipher (char* cipher) { ++ ++ FILE *fp; ++ char cmd[COMMAND_SIZE]; ++ char path[COMMAND_SIZE]; ++ ++ ++ memset(cmd, 0, COMMAND_SIZE); ++ memset(path, 0, COMMAND_SIZE); ++ ++ /* Obtain the configured cipher */ ++ ++ snprintf(cmd, COMMAND_SIZE, ++ " /bin/cat /etc/fabos/fabos.0.conf | /bin/grep 'cipher.radius' | /usr/bin/cut -c 15- "); ++ ++ fp = popen(cmd, "r"); ++ ++ if (fp == NULL) { ++ peap_log_debug("RADIUS Failed to run command to get configured cipher\n"); ++ return 0; ++ } ++ ++ fgets(path, COMMAND_SIZE, fp); ++ ++ if (strlen(path) > 1) { // if the string is not empty 1 is returned ++ snprintf(cipher, strlen(path), "%s", path); ++ pclose(fp); ++ return (1); ++ } else { ++ pclose(fp); ++ return (0); ++ } ++} ++ ++/* ++ * tls_session_t * eaptls_new_client_session ++ * ++ * Description ++ * This is the low level function which initialise the ++ * SSL session with the passed SSL context variable ++ * ++ * ++ * Input ++ * SSL context pointer ,SSL_CTX * ssl_ctx ++ * ++ * Output ++ * A new tls session is setup ++ * ++ * Return ++ * Pointer to a tls session object ++ */ ++static tls_session_t * ++eaptls_new_client_session(SSL_CTX * ssl_ctx) ++{ ++ tls_session_t * state = 0; ++ SSL * new_tls = 0; ++ char conf_cipher[COMMAND_SIZE]; ++ ++ if ((new_tls = SSL_new(ssl_ctx)) == 0) { ++ peap_log_debug("eaptls_new_client_session: Error creating new SSL"); ++ return 0; ++ } ++ ++ /* If cipher configured for RADIUS, obtain and configure */ ++ if(radpeap_verifyCipher(conf_cipher)) { ++ /* Set TLS ciphers */ ++ if( SSL_set_cipher_list(new_tls, conf_cipher)!= 1) { ++ peap_log_debug("SSL_set_cipher_list : Setting ciphers failed\n"); ++ return 0; ++ } ++ } ++ ++ /* ++ * We use the SSL's "app_data" to indicate a call-back ++ */ ++ SSL_set_app_data(new_tls, 0); ++ ++ state = (tls_session_t *)calloc(sizeof(tls_session_t), sizeof(uint8_t)); ++ if(0 == state) { ++ peap_log_debug("eaptls_new_client_session: Memory allocation failed, state is NULL \n"); ++ return 0; ++ } ++ memset(state, 0, sizeof(tls_session_t)); ++ session_init(state); ++ state->ssl = new_tls; ++ ++ /* ++ * Initialize callbacks ++ */ ++ state->record_init = record_peap_init; ++ state->record_close = record_peap_close; ++ state->record_plus = record_peap_plus; ++ state->record_minus = record_peap_minus; ++ ++ /* ++ * Create & hook the BIOs to handle the dirty side of the ++ * SSL. This is *very important* as we want to handle ++ * the transmission part. Now the only IO interface ++ * that SSL is aware of, is our defined BIO buffers. ++ * ++ * This means that all SSL IO is done to/from memory, ++ * and we can update those BIOs from the EAP packets we've ++ * received. ++ */ ++ state->into_ssl = BIO_new(BIO_s_mem()); ++ state->from_ssl = BIO_new(BIO_s_mem()); ++ SSL_set_bio(state->ssl, state->into_ssl, state->from_ssl); ++ ++ /* ++ * Add the message callback to identify what type of ++ * message/handshake is passed ++ */ ++ SSL_set_msg_callback(new_tls, cb_peap_msg); ++ SSL_set_msg_callback_arg(new_tls, state); ++ SSL_set_info_callback(new_tls, cb_peap_info); ++ ++ /* ++ * In Client mode we only connect. ++ */ ++ SSL_set_connect_state(state->ssl); ++ ++ return state; ++} ++ ++ ++/* ++ * int rad_peap_send ++ * ++ * Description ++ * Reply to the request ++ * Also attach reply attribute value pairs ++ * and any user message provided. ++ * ++ * Input ++ * RADIUS_PACKET * packet , const char *secret ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Number of octects it could really sent . ++ */ ++static int ++rad_peap_send(RADIUS_PACKET * packet, const RADIUS_PACKET * original, ++ const char * secret) ++{ ++ const char * what; ++ struct sockaddr * sa; ++ ++ /* ++ * Maybe it's a fake packet. Don't send it. ++ */ ++ if (!packet || (packet->sockfd < 0)) { ++ return 0; ++ } ++ if ((packet->code > 0) && (packet->code < 52)) { ++ what = packet_codes[packet->code]; ++ } else { ++ what = "Reply"; ++ } ++ /* ++ * And send it on it's way. ++ */ ++ ++ sa = (struct sockaddr *) peap_instance.dst_addr; ++ ++#ifndef WITH_UDPFROMTO ++ return sendto(packet->sockfd, packet->data, (int) packet->data_len, 0, ++ (struct sockaddr *) sa, ++ (sa->sa_family == AF_INET6) ? ++ sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)); ++#else ++ This needs to be fixed for IPV6 ++ { ++ struct sockaddr_in salocal; ++ memset((char *) &salocal, '\0', sizeof(salocal)); ++ salocal.sin_family = AF_INET; ++ salocal.sin_addr.s_addr = packet->src_ipaddr; ++ ++ return sendfromto(packet->sockfd, packet->data,(int) packet->data_len, 0, ++ (struct sockaddr *) &salocal, sizeof(struct sockaddr_in), ++ (struct sockaddr *) &saremote, sizeof(struct sockaddr_in)); ++ } ++#endif ++} ++ ++/* ++ * uint8_t * get_tls_data ++ * ++ * Description ++ * This function encrypts the data passed into it based on TLS ++ * TLS session parameters initaially exchanged . ++ * ++ * Input ++ * uint8_t * plain_data, uint32_t plain_data_len ++ * ++ * Output ++ * uint32_t * tls_data_len ++ * ++ * Return ++ * Returns encrypted TLS data ++ */ ++ ++uint8_t * ++get_tls_data(uint8_t * plain_data, uint32_t plain_data_len, uint32_t * tls_data_len) ++{ ++ (peap_instance.tls_session->record_init) (&peap_instance.tls_session->clean_in); ++ (peap_instance.tls_session->record_init) (&peap_instance.tls_session->dirty_out); ++ (peap_instance.tls_session->record_plus) (&peap_instance.tls_session->clean_in, ++ plain_data, plain_data_len); ++ /* Feed clean data into the SSL layer */ ++ SSL_write(peap_instance.tls_session->ssl, peap_instance.tls_session->clean_in.data, ++ peap_instance.tls_session->clean_in.used); ++ ++ /* Let SSL layer process that, get it encrypted and read back */ ++ *tls_data_len = BIO_read(peap_instance.tls_session->from_ssl, ++ peap_instance.tls_session->dirty_out.data, ++ sizeof(peap_instance.tls_session->dirty_out.data)); ++ if (*tls_data_len > 0) { ++ return peap_instance.tls_session->dirty_out.data; ++ } ++ return 0; ++} ++ ++/* ++ * uint8_t * compose_peap_identity ++ * ++ * Description ++ * This function sends the peap identity packet ++ * as part of PEAP phase 2 packet exchange, after ++ * setting up the TLS tunnel. ++ * ++ * Input ++ * int * tls_data_length, Server eap message from global peap instance ++ * ++ * Output ++ * None ++ * ++ * Return ++ * TLS Data ++ */ ++static uint8_t * ++compose_peap_identity(int * tls_data_length) ++{ ++ char eap_identity_str[MAX_NT_USERNAME] = { 0 }; ++ int eap_pkt_size = 0; ++ uint8_t * eap_message = 0; ++ uint8_t * tls_data = 0; ++ type_value_t * eap_pkt = 0; ++ ++#undef CLEAN_UP ++#define CLEAN_UP() \ ++ do { \ ++ if(eap_pkt) { free(eap_pkt); eap_pkt = 0; } \ ++ } while (0); ++ ++ *tls_data_length = 0; ++ ++ strncpy(eap_identity_str, peap_instance.user_name , strlen(peap_instance.user_name)); ++ ++ eap_pkt_size = sizeof(type_value_t) + strlen(eap_identity_str); ++ eap_message = peap_instance.srv_eap_message; ++ if (0 == eap_message) { ++ return 0; ++ } ++ eap_pkt = calloc(eap_pkt_size, sizeof(uint8_t)); ++ if (0 == eap_pkt) { ++ peap_log_debug("compose_peap_identity: Memory allocation failed "); ++ return 0; ++ } ++ eap_pkt->type = PW_EAP_IDENTITY; ++ memcpy(eap_pkt->data, eap_identity_str, strlen(eap_identity_str)); ++ /* ++ * Pass the clear eap packet and get it encrypted, Ya we can send it by our own ++ * We don't need SSL Layer to do that. ++ */ ++ tls_data = get_tls_data((uint8_t *)eap_pkt, eap_pkt_size, tls_data_length); ++ if(0 == tls_data) { ++ peap_log_debug("compose_peap_identity: Error getting tls_data\n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ CLEAN_UP(); ++ return (uint8_t*)tls_data; ++} ++ ++/* ++ * From freeradius-1.1.4 with modifications for IPv6. ++ * ++ * Receive UDP client requests, and fill in ++ * the basics of a RADIUS_PACKET structure. ++ */ ++static RADIUS_PACKET *rad_recv_ipv6(int fd) ++{ ++ RADIUS_PACKET *packet; ++ struct sockaddr_storage saremote; ++ int totallen; ++ socklen_t salen; ++ uint8_t *attr; ++ int count; ++ radius_packet_t *hdr; ++ char remote_ipaddr[INET6_ADDRSTRLEN]; ++ int seen_eap; ++#ifndef MAX_PACKET_LEN ++#define MAX_PACKET_LEN 4096 ++#endif ++#ifndef AUTH_HDR_LEN ++#define AUTH_HDR_LEN 20 ++#endif ++ uint8_t data[MAX_PACKET_LEN]; ++ int num_attributes; ++ ++ /* ++ * Allocate the new request data structure ++ */ ++ if ((packet = malloc(sizeof(RADIUS_PACKET))) == NULL) { ++ peap_log( LOG_ERR, "out of memory"); ++ return NULL; ++ } ++ memset(packet, 0, sizeof(RADIUS_PACKET)); ++ ++ /* ++ * Receive the packet. ++ */ ++ salen = sizeof(saremote); ++ memset(&saremote, 0, sizeof(saremote)); ++#ifndef WITH_UDPFROMTO ++ packet->data_len = recvfrom(fd, data, sizeof(data), ++ 0, (struct sockaddr *)&saremote, &salen); ++ packet->dst_ipaddr = htonl(INADDR_ANY); /* i.e. unknown */ ++#else ++ This needs to be fixed for IPV6 ++ { ++ socklen_t salen_local; ++ struct sockaddr_in salocal; ++ salen_local = sizeof(salocal); ++ memset(&salocal, 0, sizeof(salocal)); ++ packet->data_len = recvfromto(fd, data, sizeof(data), 0, ++ (struct sockaddr *)&saremote, &salen, ++ (struct sockaddr *)&salocal, &salen_local); ++ packet->dst_ipaddr = salocal.sin_addr.s_addr; ++ } ++#endif ++ ++ /* ++ * Check for socket errors. ++ */ ++ if (packet->data_len < 0) { ++ peap_log(LOG_ERR, "Error receiving packet: %s", ++ strerror(errno)); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * Fill IP header fields. We need these for the error ++ * messages which may come later. ++ */ ++ packet->sockfd = fd; ++ packet->src_ipaddr = htonl(INADDR_ANY); ++ ++ remote_ipaddr[0] = 0; ++ if (((struct sockaddr *) (& saremote))->sa_family == AF_INET6) ++ inet_ntop( AF_INET6, &(((struct sockaddr_in6 *) (& saremote))->sin6_addr), ++ remote_ipaddr, sizeof(remote_ipaddr)); ++ else ++ strncpy(remote_ipaddr, inet_ntoa(((struct sockaddr_in *) (& saremote))->sin_addr), sizeof(remote_ipaddr) - 1); ++ ++ packet->src_port = (((struct sockaddr *) (& saremote))->sa_family == AF_INET6) ? ++ ntohs((((struct sockaddr_in6 *) ( & saremote))->sin6_port)) : ++ ntohs((((struct sockaddr_in *) ( & saremote))->sin_port)); ++ ++ /* ++ * FIXME: Do even more filtering by only permitting ++ * certain IP's. The problem is that we don't know ++ * how to do this properly for all possible clients... ++ */ ++ ++ /* ++ * Explicitely set the VP list to empty. ++ */ ++ packet->vps = NULL; ++ ++ /* ++ * Check for packets smaller than the packet header. ++ * ++ * RFC 2865, Section 3., subsection 'length' says: ++ * ++ * "The minimum length is 20 ..." ++ */ ++ if (packet->data_len < AUTH_HDR_LEN) { ++ peap_log( LOG_ERR, "Malformed RADIUS packet from host %s:" ++ " too short (received %d < minimum %d)", ++ remote_ipaddr, packet->data_len, AUTH_HDR_LEN); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * RFC 2865, Section 3., subsection 'length' says: ++ * ++ * " ... and maximum length is 4096." ++ */ ++ if (packet->data_len > MAX_PACKET_LEN) { ++ peap_log( LOG_ERR, "Malformed RADIUS packet from host %s:" ++ " too long (received %d > maximum %d)", ++ remote_ipaddr, ++ packet->data_len, MAX_PACKET_LEN); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * Check for packets with mismatched size. ++ * i.e. We've received 128 bytes, and the packet header ++ * says it's 256 bytes long. ++ */ ++ totallen = (data[2] << 8) | data[3]; ++ hdr = (radius_packet_t *)data; ++ ++ /* ++ * Code of 0 is not understood. ++ * Code of 16 or greate is not understood. ++ */ ++ if ((hdr->code == 0) || ++ (hdr->code >= 52)) { ++ peap_log( LOG_ERR, "Bad RADIUS packet from host %s:" ++ " unknown packet code %d", ++ remote_ipaddr, ++ hdr->code); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * Repeat the length checks. This time, instead of ++ * looking at the data we received, look at the value ++ * of the 'length' field inside of the packet. ++ * ++ * Check for packets smaller than the packet header. ++ * ++ * RFC 2865, Section 3., subsection 'length' says: ++ * ++ * "The minimum length is 20 ..." ++ */ ++ if (totallen < AUTH_HDR_LEN) { ++ peap_log( LOG_ERR, "Malformed RADIUS packet from host %s:" ++ " too short (length %d < minimum %d)", ++ remote_ipaddr, ++ totallen, AUTH_HDR_LEN); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * And again, for the value of the 'length' field. ++ * ++ * RFC 2865, Section 3., subsection 'length' says: ++ * ++ * " ... and maximum length is 4096." ++ */ ++ if (totallen > MAX_PACKET_LEN) { ++ peap_log( LOG_ERR, "Malformed RADIUS packet from host %s:" ++ " too long (length %d > maximum %d)", ++ remote_ipaddr, ++ totallen, MAX_PACKET_LEN); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * RFC 2865, Section 3., subsection 'length' says: ++ * ++ * "If the packet is shorter than the Length field ++ * indicates, it MUST be silently discarded." ++ * ++ * i.e. No response to the NAS. ++ */ ++ if (packet->data_len < totallen) { ++ peap_log( LOG_ERR, "Malformed RADIUS packet from host %s:" ++ " received %d octets, packet length says %d", ++ remote_ipaddr, ++ packet->data_len, totallen); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * RFC 2865, Section 3., subsection 'length' says: ++ * ++ * "Octets outside the range of the Length field MUST be ++ * treated as padding and ignored on reception." ++ */ ++ if (packet->data_len > totallen) { ++ /* ++ * We're shortening the packet below, but just ++ * to be paranoid, zero out the extra data. ++ */ ++ memset(data + totallen, 0, packet->data_len - totallen); ++ packet->data_len = totallen; ++ } ++ ++ /* ++ * Walk through the packet's attributes, ensuring that ++ * they add up EXACTLY to the size of the packet. ++ * ++ * If they don't, then the attributes either under-fill ++ * or over-fill the packet. Any parsing of the packet ++ * is impossible, and will result in unknown side effects. ++ * ++ * This would ONLY happen with buggy RADIUS implementations, ++ * or with an intentional attack. Either way, we do NOT want ++ * to be vulnerable to this problem. ++ */ ++ attr = hdr->data; ++ count = totallen - AUTH_HDR_LEN; ++ seen_eap = 0; ++ num_attributes = 0; ++ ++ while (count > 0) { ++ /* ++ * Attribute number zero is NOT defined. ++ */ ++ if (attr[0] == 0) { ++ peap_log( LOG_ERR, ++ "Malformed RADIUS packet from host %s:" ++ " Invalid attribute 0", ++ remote_ipaddr); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * Attributes are at LEAST as long as the ID & length ++ * fields. Anything shorter is an invalid attribute. ++ */ ++ if (attr[1] < 2) { ++ peap_log( LOG_ERR, ++ "Malformed RADIUS packet from host %s:" ++ " attribute %d too short", ++ remote_ipaddr, ++ attr[0]); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * Sanity check the attributes for length. ++ */ ++ switch (attr[0]) { ++ default: /* don't do anything by default */ ++ break; ++ ++ case PW_EAP_MESSAGE: ++ seen_eap |= PW_EAP_MESSAGE; ++ break; ++ ++ case PW_MESSAGE_AUTHENTICATOR: ++ if (attr[1] != 2 + AUTH_VECTOR_LEN) { ++ peap_log( LOG_ERR, ++ "Malformed RADIUS packet from host %s:" ++ " Message-Authenticator has invalid length %d", ++ remote_ipaddr, ++ attr[1] - 2); ++ free(packet); ++ return NULL; ++ } ++ seen_eap |= PW_MESSAGE_AUTHENTICATOR; ++ break; ++ } ++ ++ /* ++ * FIXME: Look up the base 255 attributes in the ++ * dictionary, and switch over their type. For ++ * integer/date/ip, the attribute length SHOULD ++ * be 6. ++ */ ++ count -= attr[1]; /* grab the attribute length */ ++ attr += attr[1]; ++ num_attributes++; /* seen one more attribute */ ++ } ++ ++ /* ++ * If the attributes add up to a packet, it's allowed. ++ * ++ * If not, we complain, and throw the packet away. ++ */ ++ if (count != 0) { ++ peap_log( LOG_ERR, ++ "Malformed RADIUS packet from host %s:" ++ " packet attributes do NOT exactly fill the packet", ++ remote_ipaddr); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * If we're configured to look for a maximum number of ++ * attributes, and we've seen more than that maximum, ++ * then throw the packet away, as a possible DoS. ++ */ ++ if ((librad_max_attributes > 0) && ++ (num_attributes > librad_max_attributes)) { ++ peap_log( LOG_ERR, "Possible DoS attack from host %s:" ++ " Too many attributes in request" ++ " (received %d, max %d are allowed).", ++ remote_ipaddr, ++ num_attributes, librad_max_attributes); ++ free(packet); ++ return NULL; ++ } ++ ++ /* ++ * http://www.freeradius.org/rfc/rfc2869.html#EAP-Message ++ * ++ * A packet with an EAP-Message attribute MUST also have ++ * a Message-Authenticator attribute. ++ * ++ * A Message-Authenticator all by itself is OK, though. ++ */ ++ if (seen_eap && ++ (seen_eap != PW_MESSAGE_AUTHENTICATOR) && ++ (seen_eap != (PW_EAP_MESSAGE | PW_MESSAGE_AUTHENTICATOR))) { ++ peap_log( LOG_ERR, "Insecure packet from host %s:" ++ " Received EAP-Message with no Message-Authenticator.", ++ remote_ipaddr); ++ free(packet); ++ return NULL; ++ } ++ ++ if ((hdr->code > 0) && (hdr->code < 52)) { ++ peap_log_debug("rad_recv: %s packet from host %s:%d", ++ packet_codes[hdr->code], ++ remote_ipaddr); ++ } else { ++ peap_log_debug("rad_recv: Packet from host %s:%d code=%d", ++ remote_ipaddr, ++ hdr->code); ++ } ++ peap_log_debug(", id=%d, length=%d\n", hdr->id, totallen); ++ ++ /* ++ * Fill RADIUS header fields ++ */ ++ packet->code = hdr->code; ++ packet->id = hdr->id; ++ memcpy(packet->vector, hdr->vector, AUTH_VECTOR_LEN); ++ ++ /* ++ * Now that we've sanity checked the packet, we can allocate ++ * memory for it, and copy the data from the local area to ++ * the packet buffer. ++ */ ++ if ((packet->data = malloc(packet->data_len)) == NULL) { ++ free(packet); ++ peap_log( LOG_ERR, "out of memory"); ++ return NULL; ++ } ++ memcpy(packet->data, data, packet->data_len); ++ ++ return packet; ++} ++ ++/* ++ * static RADIUS_PACKET * rad_peap_recv ++ * ++ * Description ++ * Receive the radius packet , decode the eap message , ++ * find the mapped eap types inside the packet ++ * ++ * Input ++ * int sockfd ++ * ++ * Output ++ * None ++ * ++ * Return ++ * RADIUS_PACKET * rep ++ */ ++static RADIUS_PACKET * ++rad_peap_recv() ++{ ++ RADIUS_PACKET * rep = 0; ++ rep = rad_recv_ipv6(peap_instance.sockfd); ++ if(0 == rep) { ++ peap_log_debug("rad_peap_recv: rad_decode failed\n"); ++ return 0; ++ } ++ if ((rad_decode(rep, 0, peap_instance.shared_secret)) != 0) { ++ peap_log_debug("rad_peap_recv: rad_decode failed\n"); ++ return 0; ++ } ++ /* ++ * okay got back the packet, go and decode the EAP-Message. ++ */ ++ unmap_eap_types(rep); ++ return rep; ++} ++ ++/* ++ * int setup_TLS_session ++ * ++ * Description ++ * This function setup and initialize TLS session variables ++ * and necessary call backs ++ * ++ * Input ++ * None ++ * ++ * Output ++ * Global peap_instance's TLS context variables are updated ++ * ++ * Return ++ * Sucess or Failure ++ */ ++static int ++setup_TLS_session(void) ++{ ++ SSL_METHOD * meth; ++ SSL_CTX * ctx; ++ int ret = 0; ++ /* ++ * No need to check the return value since it always returns 1 ++ */ ++ ret = SSL_library_init(); ++ SSL_load_error_strings(); ++ meth = SSLv23_method(); /* Connection method for client */ ++ if (0 == meth) { ++ peap_log_debug("setup_TLS_session: Error while setting up TLSv1_method"); ++ return (-1); ++ } ++ /* Set up an SSL context object, which will be used for SSL session establishment */ ++ ctx = SSL_CTX_new(meth); ++ if (0 == ctx) { ++ peap_log_debug("setup_TLS_session: Error while setting up SSL infrastructure"); ++ return (-1); ++ } ++ peap_instance.tls_session = eaptls_new_client_session(ctx); ++ if (0 == peap_instance.tls_session) { ++ return (-1); ++ } ++ return 0; ++} ++ ++/* ++ * RADIUS_PACKET * rad_peap_compose_ack ++ * ++ * Description ++ * This function composes a tls fragment handshake ++ * when a `more fragment' bit set packet is received ++ * Input ++ * RADIUS_PACKET * req ++ * ++ * Output ++ * None ++ * ++ * Return ++ * RADIUS_PACKET * req ++ */ ++static RADIUS_PACKET * ++rad_peap_compose_ack(RADIUS_PACKET * req) ++{ ++ char mesg_auth[AUTH_VECTOR_LEN] = { 0 }; ++ uint16_t eap_pkt_size = 0; ++ uint16_t eap_msg_size = 0; ++ uint16_t rad_pkt_size = 0; ++ uint16_t tv_size = 0; ++ uint16_t state_tv_size = 0; ++ uint16_t temp_len = 0; ++ char * ptr = 0; ++ uint8_t * eap_message = 0; ++ eap_message_t * eap_msg = 0; ++ eap_tls_ack_t * eap_pkt = 0; ++ type_length_value_t * tv = 0; ++ type_length_value_t * state_tv = 0; ++ VALUE_PAIR * eap_state; ++ RADIUS_PACKET * rep = 0; ++ radius_packet_t * rad_pkt = 0; ++ ++#undef CLEAN_UP ++#define CLEAN_UP() \ ++ do { \ ++ if(eap_pkt) { free(eap_pkt); eap_pkt = 0; } \ ++ if(eap_msg) { free(eap_msg); eap_msg = 0; } \ ++ if(state_tv) { free(state_tv); state_tv = 0; } \ ++ if(tv) { free(tv); tv = 0; } \ ++ } while (0); ++ ++ rep = peap_instance.server_rep; ++ eap_pkt_size = sizeof(eap_tls_ack_t); ++ tv_size = sizeof(type_length_value_t) + AUTH_VECTOR_LEN; ++ ++ /* ++ * Extract PW_EAP_MESSAGE ++ */ ++ eap_message = peap_instance.srv_eap_message; ++ if (0 == eap_message) { ++ peap_log_debug("rad_peap_compose_ack: eap_message is NULL"); ++ return 0; ++ } ++ eap_pkt = calloc(eap_pkt_size, sizeof(uint8_t)); ++ if(0 == eap_pkt) { ++ peap_log_debug("rad_peap_compose_ack:Memory allocation failed, eap_pkt is NULL\n"); ++ return 0; ++ } ++ eap_pkt->code = PW_EAP_RESPONSE; ++ eap_pkt->id = (uint8_t) (*(eap_message + 1)); ++ temp_len = htons(eap_pkt_size); ++ eap_pkt->length = temp_len; ++ eap_pkt->type = PW_EAP_PEAP; ++ ++ /* Let it be a TLS fragment ack packet */ ++ eap_pkt->flags = EAPTLS_ACK; ++ eap_msg_size = sizeof(eap_message_t) + eap_pkt_size; ++ eap_msg = calloc(eap_msg_size, sizeof(uint8_t)); ++ if(0 == eap_msg) { ++ peap_log_debug("rad_peap_compose_ack:Memory allocation failed, eap_msg is NULL\n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ eap_msg->code = PW_EAP_MESSAGE; ++ memcpy(eap_msg->data, (eap_tls_ack_t *) eap_pkt, eap_pkt_size); ++ eap_msg->length = eap_msg_size; ++ ++ ++ /* Look for state tlv in the server reply packet, ++ * so that we can copy that to client request packet ++ */ ++ eap_state = pairfind(rep->vps, PW_STATE); ++ if (0 == eap_state) { ++ peap_log_debug("rad_peap_compose_ack: Error while getting PW_STATE"); ++ CLEAN_UP(); ++ return 0; ++ } ++ ++ /* State tlv, state is send by the server, Just copy it into each packet */ ++ state_tv_size = sizeof(type_length_value_t) + eap_state->length; ++ state_tv = calloc(state_tv_size, sizeof(uint8_t)); ++ if(0 == state_tv) { ++ peap_log_debug("rad_peap_compose_ack:Memory allocation failed, state_tv is NULL\n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ state_tv->type = PW_STATE; ++ state_tv->length = state_tv_size; ++ memcpy(state_tv->data, eap_state->strvalue, eap_state->length); ++ ++ tv_size = sizeof(type_length_value_t) + AUTH_VECTOR_LEN; ++ tv = calloc(tv_size, sizeof(uint8_t)); ++ if(0 == tv) { ++ peap_log_debug("rad_peap_compose_ack:Memory allocation failed, tv is NULL\n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ tv->type = PW_MESSAGE_AUTHENTICATOR; ++ tv->length = tv_size; ++ rad_pkt_size = sizeof(radius_packet_t) + eap_msg_size + ++ state_tv_size + tv_size; ++ ++ rad_pkt = calloc(rad_pkt_size, sizeof(uint8_t)); ++ if(0 == rad_pkt) { ++ peap_log_debug("rad_peap_compose_ack:Memory allocation failed, rad_pkt is NULL\n"); ++ CLEAN_UP(); ++ return 0; ++ } ++ ++ /* Client can only send Radius Access Requests */ ++ rad_pkt->code = RADIUS_ACCESS_REQUEST; /* Radius Access-Request */ ++ rad_pkt->id = rep->id + 1; /* Packet ID should different for each EAP request*/ ++ ++ memset(rad_pkt->vector, 0, AUTH_VECTOR_LEN); ++ ++ /* Copy the EAP Message portion into the packet */ ++ ptr = (uint8_t *) rad_pkt->data; ++ memcpy(ptr, eap_msg, eap_msg_size); ++ ++ temp_len = htons(rad_pkt_size); ++ rad_pkt->length = temp_len; ++ ++ /* Calculate the md5 hash for the vector and pack into the packet */ ++ librad_md5_calc(req->vector, rep->vector, sizeof(rep->vector)); ++ memcpy(rad_pkt->vector, req->vector, sizeof(rad_pkt->vector)); ++ ++ /* Copy the state tlv */ ++ ptr += eap_msg_size; ++ memcpy(ptr, state_tv, state_tv_size); ++ ++ /* Copy the message authenticator */ ++ ptr += state_tv_size; ++ memcpy(ptr, tv, tv_size); ++ ++ lrad_hmac_md5((uint8_t *) rad_pkt, rad_pkt_size, peap_instance.shared_secret, ++ strlen(peap_instance.shared_secret), mesg_auth); ++ ptr += 2; /* ++ * Skip the length field, Now its points to the data part ++ */ ++ memcpy(ptr, mesg_auth, AUTH_VECTOR_LEN); ++ req->data = (uint8_t *) rad_pkt; ++ req->data_len = rad_pkt_size; ++ CLEAN_UP(); ++ return req; ++} ++ ++/* ++ * uint8_t * extract_tls_data ++ * ++ * Description ++ * This function extracts the TLS layer data from eap message data ++ * ++ * Input ++ * eap message data from global peap_instance ++ * ++ * Output ++ * int *data_len , uint8_t * more_tls_data ++ * ++ * Return ++ * Tls data ++ */ ++static uint8_t * ++extract_tls_data(int * data_len, uint8_t * more_tls_data) ++{ ++ uint16_t tls_data_len = 0; ++ uint8_t * eap_message = 0; ++ uint8_t * tls_data = 0; ++ ++ /* Get a local pointer to the eap message */ ++ eap_message = peap_instance.srv_eap_message; ++ if (0 == eap_message) { ++ peap_log_debug("extract_tls_data: eap_message is NULL"); ++ return (0); ++ } ++ /* Check for `Length Included' flag */ ++ if (((eap_tls_response_t *) eap_message)->flags & EAP_LENGTH_INCLUDED) { ++ tls_data = (uint8_t *) ((eap_tls_response_t *)eap_message)->data; ++ tls_data_len = ((eap_tls_response_t *)eap_message)->length; ++ tls_data_len = ntohs(tls_data_len) - TLS_HDR_LEN_PLUS_LENGTH; ++ ++ } else { ++ /* `Length Included' flag is clear */ ++ tls_data = (uint8_t *)(&(((eap_tls_response_t *) eap_message)->tls_msg_len)); ++ tls_data_len = ((eap_tls_response_t *) eap_message)->length; ++ tls_data_len = ntohs(tls_data_len) - TLS_HDR_LEN; ++ } ++ /* Check `more tls data' bit is set or not */ ++ if (((eap_tls_response_t *) eap_message)->flags & MORE_TLS_DATA) { ++ *more_tls_data = 1; ++ } else { ++ *more_tls_data = 0; ++ } ++ *data_len = tls_data_len; ++ return tls_data; ++} ++ ++/* ++ * uint8_t * process_peap_message ++ * ++ * Description ++ * This function get the unencrypted peap data ++ * out of encrypted tls data ++ * ++ * Input ++ * Gets the encrypted tls data from global peap_instance ++ * ++ * Output ++ * Unencryted data from tls layer ++ * ++ * Return ++ * int *data_length ++ */ ++static uint8_t * ++process_peap_message(int * data_length) ++{ ++ uint8_t more_peap_data = 0; ++ uint16_t client_state = 0; ++ int peap_data_length = 0; ++ int read_bytes = 0; ++ uint8_t * peap_data = 0; ++ ++ peap_data = extract_tls_data((int *) &peap_data_length, (uint8_t *) & more_peap_data); ++ if (0 == peap_data) { ++ peap_log_debug("process_peap_message: Error while extracting PEAP data: state 0x%x \n", ++ client_state); ++ peap_log_debug("process_peap_message: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return 0; ++ } ++ /* Pass it on to tls layer */ ++ (peap_instance.tls_session->record_init)(&peap_instance.tls_session->dirty_in); ++ (peap_instance.tls_session->record_init)(&peap_instance.tls_session->clean_out); ++ (peap_instance.tls_session->record_plus)(&peap_instance.tls_session->dirty_in, ++ peap_data, peap_data_length); ++ /* Feed the encrypted TLS data into SSL layer */ ++ BIO_write(peap_instance.tls_session->into_ssl, peap_instance.tls_session->dirty_in.data, peap_data_length); ++ /* Get clean data out of SSL layer */ ++ read_bytes = SSL_read(peap_instance.tls_session->ssl, ++ peap_instance.tls_session->clean_out.data, ++ sizeof(peap_instance.tls_session->clean_out.data)); ++ ++ if (read_bytes <= 0) { ++ peap_log_debug("process_peap_message: Error while decoding PEAP" ++ " data or no PEAP data: state 0x%x \n", client_state); ++ peap_log_debug("process_peap_message: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return 0; ++ } ++ *data_length = read_bytes; ++ return (peap_instance.tls_session->clean_out.data); ++} ++ ++/* ++ * int process_eap_message ++ * ++ * Description ++ * This function verify's what kind of eap packet we recieved ++ * To get the eap message , it refers to the global insatnce ++ * Input ++ * Takes input from global peap_instace ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Sucess or failure ++ */ ++static int ++process_eap_message(void) ++{ ++ uint8_t * eap_msg = 0; ++ uint8_t eap_code = 0; ++ uint8_t eap_type = 0; ++ ++ /* ++ * Retrieve the EAP message from the server response ++ */ ++ eap_msg = (uint8_t *) peap_instance.srv_eap_message; ++ if (0 == eap_msg) { ++ peap_log_debug("process_eap_message: srv_eap_message is NULL!!!"); ++ return (-1); ++ } ++ eap_code = ((eap_tls_response_t *) eap_msg)->code; /* Get EAP code */ ++ eap_type = ((eap_tls_response_t *) eap_msg)->type; ++ ++ switch (eap_code) { ++ case PW_EAP_REQUEST: ++ /* Are we talking in PEAP ? */ ++ switch (eap_type) { ++ case PW_EAP_PEAP: ++ return 0; /* Yes!! We do talk in PEAP, If Not.. Die !!! */ ++ case PW_EAP_SECUREID: ++ return (PAM_AUTH_SECUREID); ++ case PW_EAP_MSCHAPV2: ++ case PW_EAP_IDENTITY: ++ case PW_EAP_NAK: ++ default: ++ peap_log_debug("process_eap_message: Invalid EAP type 0x%x \n", eap_type); ++ return (-1); ++ } ++ case PW_EAP_SUCCESS: ++ case PW_EAP_FAILURE: ++ default: ++ peap_log_debug("process_eap_message: Invalid EAP code 0x%x \n", eap_code); ++ return (-1); ++ } ++ return (-1); ++} ++ ++/* ++ * int extract_eap_message ++ * ++ * Description ++ * Extract the EAP-MESSAGE AVP from the ++ * radius packet and update into the global `peap_instance' ++ * ++ * Input ++ * None ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Success or Failure ++ */ ++static int ++extract_eap_message(void) ++{ ++ uint16_t len = 0; ++ int total_len = 0; ++ uint8_t * eap_message = 0; ++ uint8_t * ptr = 0; ++ VALUE_PAIR * first_vp = 0; ++ VALUE_PAIR * tmp_vp = 0; ++ VALUE_PAIR * vp = 0; ++ RADIUS_PACKET * rep = 0; ++ ++ /* Get the latest server reply */ ++ rep = peap_instance.server_rep; ++ if (0 == rep) { ++ peap_log_debug("extrace_eap_message: server rep is NULL!!!"); ++ return (-1); ++ } ++ /* ++ * Extract PW_EAP_MESSAGE ++ */ ++ first_vp = pairfind(rep->vps, PW_EAP_MESSAGE); ++ if (0 == first_vp) { ++ peap_log_debug("extrace_eap_message: server first_vp is NULL!!!"); ++ return (-1); ++ } ++ memcpy(&len, first_vp->strvalue + 2, sizeof(len)); ++ len = ntohs(len); ++ /* ++ * Compute the entire EAP message length ++ */ ++ for (vp = first_vp; vp != 0; vp = vp->next) { ++ tmp_vp = pairfind(vp, PW_EAP_MESSAGE); ++ if (0 == tmp_vp) { ++ break; ++ } ++ total_len += tmp_vp->length; ++ } ++ /* ++ * Check for error condition ++ */ ++ if (total_len > len) { ++ peap_log_debug("extract_eap_message: Improper EAP message length"); ++ return (-1); ++ } ++ ++ eap_message = calloc(total_len, sizeof(uint8_t)); ++ if (0 == eap_message) { ++ peap_log_debug("extract_eap_message: Memory allocation failed for eap_message"); ++ return (-1); ++ } ++ ptr = eap_message; ++ /* ++ * Extract whole EAP message present in the server response ++ */ ++ for (vp = first_vp; vp != 0; vp = vp->next) { ++ tmp_vp = pairfind(vp, PW_EAP_MESSAGE); ++ if (0 == tmp_vp) { ++ break; ++ } ++ memcpy(ptr, tmp_vp->strvalue, tmp_vp->length); ++ ptr += tmp_vp->length; ++ } ++ /* Free the prevoius EAP message */ ++ if(peap_instance.srv_eap_message) ++ free(peap_instance.srv_eap_message); ++ ++ /* Update into global peap_instance */ ++ peap_instance.srv_eap_message = eap_message; ++ return 0; ++} ++ ++/* ++ * int process_rad_rep ++ * ++ * Description ++ * Process the radiud reply from server , ++ * checks for Access-Reject ++ * ++ * Input ++ * None ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Success or failure , 0 or -1 ++ */ ++static int ++process_rad_rep() ++{ ++ RADIUS_PACKET * rep = 0; ++ RADIUS_PACKET * req = 0; ++ ++ rep = peap_instance.server_rep; ++ req = peap_instance.current_rad_pkt; ++ /* ++ * Check for access-reject message ++ */ ++ if (PW_AUTHENTICATION_REJECT == rep->code) { ++ peap_log_debug("process_rad_rep: Access Reject received: state 0x%x \n", ++ peap_instance.client_state); ++ peap_log_debug("process_rad_rep: Dumping the req & rep packets"); ++ return (-1); ++ } ++ return 0; ++} ++ ++/* ++ * void compose_radius_pkt ++ * ++ * Description ++ * Add the necessary fields to the ++ * radius data structure maintained .. ++ * This function does not really compose a radius packet ++ * ++ * Input ++ * RADIUS_PACKET * req , int rad_code ++ * ++ * Output ++ * None ++ * ++ * Return ++ * None ++ */ ++void ++compose_radius_pkt(RADIUS_PACKET * req, int rad_code) ++{ ++ req->code = rad_code; ++ req->dst_ipaddr = htonl(INADDR_ANY); ++ req->dst_port = ((peap_instance.dst_addr)->sa_family == AF_INET6) ? ++ ntohs((((struct sockaddr_in6 *) (peap_instance.dst_addr))->sin6_port)) : ++ ntohs((((struct sockaddr_in *) (peap_instance.dst_addr))->sin_port)); ++ req->sockfd = peap_instance.sockfd; ++} ++ ++/* ++ * int peap_add_to_list ++ * ++ * Description ++ * Add the tls fragments into the linked list of tls fragments ++ * ++ * Input ++ * list , data , len ++ * ++ * Output ++ * None ++ * ++ * Return ++ * success or failure ++ */ ++static int ++peap_add_to_list(tls_data_list_t * list, uint8_t * data, int len) ++{ ++ tls_data_list_t *tls_element = 0; ++ ++#undef CLEAN_UP ++#define CLEAN_UP() \ ++ do { \ ++ if(tls_element) { \ ++ free(tls_element); \ ++ tls_element = 0; \ ++ } \ ++ } while (0); ++ ++ tls_element = calloc(sizeof(tls_data_list_t), sizeof(uint8_t)); ++ if (0 == tls_element) { ++ peap_log_debug("peap_add_to_list: Error while allocating memory for tls_element"); ++ return -1; ++ } ++ tls_element->tls_data = calloc(len, sizeof(uint8_t)); ++ if (0 == tls_element->tls_data) { ++ peap_log_debug("peap_add_to_list: Error while allocating memory tls_element data"); ++ CLEAN_UP(); ++ return -1; ++ } ++ /* Add up the TLS fragments into a link list */ ++ memcpy(tls_element->tls_data, data, len); ++ tls_element->tls_data_length = len; ++ tls_element->next = 0; ++ if (0 == list->list_end) { ++ list->next = list->list_end = tls_element; ++ } else { ++ list->list_end->next = tls_element; ++ list->list_end = tls_element; ++ } ++ return 0; ++} ++ ++/* ++ * uint8_t * peap_remove_from_list ++ * ++ * Description ++ * Remove the tls fragments queue nodes ,once we receive ++ * all the tls fragments ++ * ++ * Input ++ * tls_data_list_t * list ++ * ++ * Output ++ * int * len ++ * ++ * Return ++ * Return tls data ++ */ ++static uint8_t * ++peap_remove_from_list(tls_data_list_t * list, int *len) ++{ ++ uint8_t * data = 0; ++ tls_data_list_t * tls_entry = 0; ++ ++ *len = 0; ++ tls_entry = list->next; ++ if (0 != tls_entry) { ++ /* ++ * Free the nodes as they get removed ++ */ ++ list->next = tls_entry->next; ++ data = tls_entry->tls_data; ++ *len = tls_entry->tls_data_length; ++ } else { ++ /* ++ * We are at the end of the list ++ */ ++ list->list_end = list->next = 0; ++ } ++ free(tls_entry); ++ return data; ++} ++ ++/* ++ * uint8_t * compose_mschap_response ++ * ++ * Description ++ * Function builds an mschap response packet from the ++ * access challenge send by the server based on the ++ * state machine's state ++ * ++ * Input ++ * Challenge issued by server ,username ,password ++ * ++ * Output ++ * Composed mschap reply packets's length ++ * ++ * Return ++ * Returns the inner tlv peap data ++ */ ++static uint8_t * ++compose_mschap_response(int * mschap_rep_len) ++{ ++ char passwd[MAX_NT_PASSWORD] = { 0 }; ++ char username[MAX_NT_USERNAME] = { 0 }; ++ char gen_auth_response[MAX_AUTH_RESPONSE_LEN * 2] = { 0 }; ++ char recv_auth_response[MAX_AUTH_RESPONSE_LEN * 2] = { 0 }; ++ unsigned char auth_response[MAX_AUTH_RESPONSE_LEN] = { 0 }; ++ uint8_t count = 0; ++ uint8_t nthash = 0; ++ uint16_t res_len = 0 ; ++ uint8_t * ptr = 0; ++ VALUE_PAIR * first_vp = 0; ++ mschapv2_request_packet_t * mschap_req = 0; ++ mschapv2_response_packet_t * mschap_rep = 0; ++ RADIUS_PACKET * rep = 0; ++ ++ strncpy(passwd, peap_instance.user_passwd, strlen(peap_instance.user_passwd)); ++ strncpy(username, peap_instance.user_name, strlen(peap_instance.user_name)); ++ ++ /* Get the latest server reply */ ++ rep = peap_instance.server_rep; ++ if (0 == rep) { ++ peap_log_debug("compose_mschap_response : server rep is NULL!!!"); ++ return 0; ++ } ++ /* Extract the EAP Message from the AVP's */ ++ first_vp = pairfind(rep->vps, PW_EAP_MESSAGE); ++ if (0 == first_vp) { ++ peap_log_debug("compose_mschap_response : %d: first_vp is NULL \n", ++ __LINE__); ++ return 0; ++ } ++ if ((CLIENT_TLS_ESTABLISHED | CLIENT_RECEIVE_AUTH_RESULT) == peap_instance.client_state) { ++ /* ++ * Get the authenticator response ++ */ ++ memcpy(recv_auth_response,&(peap_instance.tls_session-> clean_out.data[7]), MAX_AUTH_RESPONSE_LEN * 2); ++ /* ++ * Generate authenticator response ++ */ ++ GenerateAuthenticatorResponse(passwd, cached_nt_response, cached_peer_challenge,cached_auth_challenge, ++ username, auth_response, nthash); ++ /* ++ * Encode the 20 bytes response to 40 bytes ascii ++ */ ++ for (count = 0; count < MAX_AUTH_RESPONSE_LEN; count++) { ++ encode_asc(auth_response[count],&gen_auth_response[count * 2]); ++ } ++ /* ++ * Verify the authenticator response ++ */ ++ if (0 != strncmp(gen_auth_response, recv_auth_response, ++ MAX_AUTH_RESPONSE_LEN * 2)) { ++ peap_log_debug("compose_mschap_response: WARNING!!!!Authenticator response doesnt match!!!!"); ++ return 0; ++ } ++ ++ *mschap_rep_len = 1 /* PW_EAP_MSCHAPV2 */ + 1 /* Mschap opcode size*/; ++ ++ ptr = (uint8_t *)calloc(*mschap_rep_len, sizeof(uint8_t)); ++ if (0 == ptr) { ++ peap_log_debug("compose_mschap_response: Memory allocation failed "); ++ return 0; ++ } ++ *ptr = PW_EAP_MSCHAPV2; /*EAP-Type */ ++ *(ptr + 1) = PW_EAP_MSCHAPV2_SUCCESS; /*Mschap opcode */ ++ ++ return ptr; /* Return from here itself, don't fall through */ ++ ++ } /* else part */ ++ ++ *mschap_rep_len = 1 /* Type PW_EAP_MSCHAPV2 */ + ++ sizeof(mschapv2_response_packet_t) - 1 + ++ strlen(peap_instance.user_name); ++ ++ (*mschap_rep_len) += 1; ++ /* Ayeee!!! ^^Hack^^ Added for Null terminating user-name string ++ * Here W2K3 Server deviates from RFC ... ++ */ ++ ++ ptr = calloc(*mschap_rep_len, sizeof(uint8_t)); ++ if (0 == ptr) { ++ peap_log_debug("compose_mschap_response: Memmory allocation failed"); ++ return 0; ++ } ++ *ptr = PW_EAP_MSCHAPV2; ++ mschap_rep = (mschapv2_response_packet_t *) (ptr + 1 ); /*Skip Eap-Type*/ ++ mschap_rep->opcode = PW_EAP_MSCHAPV2_RESPONSE; ++ mschap_rep->mschapv2_id = *((uint8_t *) (first_vp->strvalue + 1)); ++ ++ res_len = htons(*mschap_rep_len); ++ memcpy(mschap_rep->ms_length, &res_len, sizeof(uint16_t)); ++ mschap_rep->value_size = MSCHAP_RESPONSE_VALUE_SIZE & 0xff; ++ mschap_req = (mschapv2_request_packet_t *)((peap_instance.tls_session->clean_out.data) + 1); ++ ++ for (count = 0; count < MSCHAPV2_CHALLENGE_LEN; count++) { ++ mschap_rep->peer_challenge[count] = lrad_rand(); ++ /* Just random numbers, Thats it !! */ ++ } ++ memset(mschap_rep->reserved, 0, MS_RESERVED_BYTES); /* 8 bytes */ ++ ++ /* Get Ms-Chap response as per the RFC */ ++ GenerateNTResponse(mschap_req->auth_challenge, mschap_rep->peer_challenge, ++ username, passwd, mschap_rep->nt_response, nthash); ++ /* Cache the challenges and responses for future use */ ++ memcpy(cached_auth_challenge, mschap_req->auth_challenge, ++ MAX_CHALLENGE_LEN); ++ memcpy(cached_peer_challenge, mschap_rep->peer_challenge, ++ MAX_CHALLENGE_LEN); ++ memcpy(cached_nt_response, mschap_rep->nt_response, MAX_RESPONSE_LEN); ++ mschap_rep->flags = 0x0; /* Clear Flag field */ ++ memcpy(mschap_rep->data, username, strlen(username)); ++ return ptr; ++} ++ ++/* ++ * int wait_for_server_response ++ * ++ * Description ++ * Wait for the server response for the server specific timeout values ++ * Maintain the value of `retreis' between the function calls ++ * ++ * Input ++ * None ++ * ++ * Output ++ * None ++ * ++ * Return ++ * PEAP_SERVER_RETRY or Success or Failure ++ */ ++int ++wait_for_server_response() ++{ ++ time_t now = { 0 }; ++ time_t end = { 0 }; ++ struct timeval tv = { 0 }; ++ fd_set set; ++ radius_server_t * server = 0; ++ int ret = 0; ++ ++ server = peap_instance.cur_serv; ++ if (0 == server) { ++ peap_log_debug("wait_for_server_response: cur_serv is NULL \n"); ++ return(-1); ++ } ++ ++ /* wait for the server specific timeout */ ++ tv.tv_sec = server->timeout; ++ tv.tv_usec = 0; ++ ++ /* Clear out the set */ ++ FD_ZERO(&set); ++ /* wait only for the RADIUS UDP socket */ ++ FD_SET(peap_instance.sockfd, &set); ++ ++ time(&now); ++ end = now + tv.tv_sec; ++ ret = select(peap_instance.sockfd + 1, &set, NULL, NULL, &tv); ++ ++ /* ++ * Is it timed out??? ++ */ ++ if (0 == ret) { ++ peap_log_debug("wait_for_server_response: Server timed out. peap_instance.retriesleft %d \n", ++ peap_instance.retries); ++ if (--(peap_instance.retries)) { ++ return PEAP_SERVER_RETRY; ++ } else { ++ return PAM_AUTHINFO_UNAVAIL; ++ } ++ } ++ /* ++ * Interrupted or error ++ */ ++ if (ret < 0) { ++ peap_log_debug("wait_for_server_response: Error in accessing the server \n"); ++ return -1; ++ } ++ if (FD_ISSET(peap_instance.sockfd, &set)) { ++ peap_log_debug("wait_for_server_response: Server responded. Moving ahead \n"); ++ return 0; ++ } ++ return 0; ++} ++/* ++ *int handle_req_rep ++ * ++ * Description ++ * This function handles the request and reply (response) ++ * excahnged between peap client and server ++ * It maintains a state machine ++ * Input ++ * None ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Retuns Success or Failure ++ */ ++static int ++handle_req_rep(void) ++{ ++ uint8_t exit = FALSE; ++ uint8_t more_tls_data = 0; ++ uint8_t result_avp[11] = { 0 }; ++ uint16_t client_state = 0; ++ int read_bytes = 0; ++ int tls_data_length = 0; ++ int peap_data_length = 0; ++ int total_length = 0; ++ int ret = 0; ++ uint8_t * tls_data = 0; ++ uint16_t temp_len = 0; ++ uint8_t * peap_data = 0; ++ RADIUS_PACKET * req = 0; ++ RADIUS_PACKET * rep = 0; ++ int retval = 0; ++ ++ /* Take the initial cleint_state, to start with we user it here */ ++ client_state = peap_instance.client_state; ++ ++ /* ++ * Peap client state machine is maintained here ++ * For easy understandabillity, States are given in the order they transit ++ * form one to other, from top to bottom ++ */ ++ do { ++ switch (client_state) { ++ case CLIENT_INIT_STATE: ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ ++ /* Build the first EAP idendity packet */ ++ ret = compose_eap_identity_pkt(req); ++ if (0 != ret) { ++ peap_log_debug("handle_req_rep: Error while composing EAP identity pkt: state 0x%x \n", ++ client_state); ++ free(req); ++ return (-1); ++ } ++ peap_instance.current_rad_pkt = peap_instance.prev_rad_pkt = req; ++ client_state = peap_instance.client_state = CLIENT_EAP_CONNECT; ++ break; ++ case CLIENT_EAP_CONNECT: ++ /* Now we have a server reply, go and process */ ++ ret = process_eap_message(); ++ ++ peap_log_debug("handle_req_rep: after calling process_eap_message \n"); ++ if (ret < 0) { ++ peap_log_debug("handle_req_rep: Error while processing radius response: state 0x%x \n", ++ client_state); ++ if (ret == PAM_AUTH_SECUREID) { ++ peap_log_debug("handle_req_rep:ret%d\n", ret); ++ return (PAM_AUTH_SECUREID); ++ } ++ return (-1); ++ } ++ ret = setup_TLS_session(); ++ ++ peap_log_debug("handle_req_rep: after calling setup_TLS_session \n"); ++ if (ret < 0) { ++ peap_log_debug("handle_req_rep: Error while setting up TLS session: state 0x%x \n", ++ client_state); ++ return (-1); ++ } ++ peap_log_debug("handle_req_rep: before record_init \n"); ++ ++ (peap_instance.tls_session->record_init)(&peap_instance.tls_session->dirty_out); ++ ++ /* ++ * Send Client Hello ++ */ ++ read_bytes = SSL_read(peap_instance.tls_session->ssl, ++ peap_instance.tls_session->dirty_out.data, ++ sizeof(peap_instance.tls_session->dirty_out.data)); ++ peap_log_debug("handle_req_rep: after calling SSL_read \n"); ++ ++ read_bytes = BIO_read(peap_instance.tls_session->from_ssl, ++ peap_instance.tls_session->dirty_out.data, ++ sizeof(peap_instance.tls_session->dirty_out.data)); ++ peap_log_debug("handle_req_rep: after calling BIO_read \n"); ++ ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ return (-1); ++ } ++ peap_log_debug("handle_req_rep: after calling rad_alloc \n"); ++ ++ /* Get all details about radius server */ ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ ++ peap_log_debug("handle_req_rep: after calling compose_radius_pkt \n"); ++ ++ /* Compose radius packet packet on-wire */ ++ req = compose_eap_packet(req,peap_instance.tls_session->dirty_out.data, ++ read_bytes, TRUE); ++ peap_log_debug("handle_req_rep: after calling compose_eap_packet \n"); ++ ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ free(req); ++ return (-1); ++ } ++ /* Keep the SSL buffers updated */ ++ (peap_instance.tls_session->record_init)(&peap_instance.tls_session->dirty_out); ++ peap_instance.prev_rad_pkt = peap_instance.current_rad_pkt; ++ peap_instance.current_rad_pkt = req; ++ client_state = peap_instance.client_state = CLIENT_TLS_HANDSHAKE; ++ break; ++ case CLIENT_TLS_HANDSHAKE: ++ ret = process_eap_message(); ++ if (ret < 0) { ++ peap_log_debug("handle_req_rep: Error while processing radius response: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ if (ret == PAM_AUTH_SECUREID) { ++ return (PAM_AUTH_SECUREID); ++ } ++ return (-1); ++ } ++ /* Get the tls data out of eap message */ ++ tls_data = extract_tls_data((int *) &tls_data_length, (uint8_t *) & more_tls_data); ++ if (0 == tls_data) { ++ peap_log_debug("handle_req_rep: Error while extracting TLS data: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, ++ SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ /* Wait untill till all TLS fragments are received, now we can proceed */ ++ if (0 == more_tls_data) { ++ (peap_instance.tls_session->record_init)(&peap_instance.tls_session->dirty_in); ++ peap_add_to_list(&peap_instance.srv_tls_data_list, tls_data, tls_data_length); ++ while (TRUE) { ++ tls_data = peap_remove_from_list(&peap_instance.srv_tls_data_list, ++ &tls_data_length); ++ if (0 == tls_data) { ++ break; ++ } ++ (peap_instance.tls_session->record_plus)(&peap_instance.tls_session->dirty_in, ++ tls_data, tls_data_length); ++ free(tls_data); ++ total_length += tls_data_length; ++ } ++ } ++ if (1 == more_tls_data) { ++ peap_add_to_list(&peap_instance.srv_tls_data_list, tls_data, tls_data_length); ++ client_state = peap_instance.client_state = (CLIENT_TLS_HANDSHAKE | CLIENT_SEND_ACK); ++ continue; /* Loop in these two states untill we get all TLS fragments */ ++ } ++ BIO_write(peap_instance.tls_session->into_ssl, peap_instance.tls_session->dirty_in.data, ++ total_length); ++ total_length = 0; ++ (peap_instance.tls_session->record_init)(&peap_instance.tls_session->clean_out); ++ read_bytes = SSL_read(peap_instance.tls_session->ssl, ++ peap_instance.tls_session->clean_out.data, ++ sizeof(peap_instance.tls_session->clean_out.data)); ++ if (read_bytes > 0) { ++ peap_instance.tls_session->clean_out.used = read_bytes; ++ } ++ /* Get the dirty data from SSL layer */ ++ ++ read_bytes = BIO_read(peap_instance.tls_session->from_ssl, ++ peap_instance.tls_session->clean_out.data, ++ sizeof(peap_instance.tls_session->clean_out.data)); ++ if (TLS_ST_OK == SSL_get_state(peap_instance.tls_session->ssl)) { ++ /* ++ * We are now done with establishing TLS channel. ++ */ ++ client_state = peap_instance.client_state = (CLIENT_TLS_ESTABLISHED | CLIENT_PEAP_START); ++ free(tls_data); ++ continue; ++ } ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ req = compose_eap_packet(req, peap_instance.tls_session->clean_out.data, ++ read_bytes, TRUE); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", client_state, ++ SSL_get_state(peap_instance.tls_session->ssl)); ++ free(req); ++ return (-1); ++ } ++ peap_instance.prev_rad_pkt = peap_instance.current_rad_pkt; ++ peap_instance.current_rad_pkt = req; ++ break; ++ case (CLIENT_TLS_HANDSHAKE | CLIENT_SEND_ACK): ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ req = rad_peap_compose_ack(req); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing TLS ack: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ free(req); ++ return (-1); ++ } ++ peap_instance.prev_rad_pkt = peap_instance.current_rad_pkt; ++ peap_instance.current_rad_pkt = req; ++ client_state = peap_instance.client_state = CLIENT_TLS_HANDSHAKE; ++ break; ++ case (CLIENT_TLS_ESTABLISHED | CLIENT_PEAP_START): ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ tls_data = 0; ++ tls_data_length = 0; ++ peap_instance.prev_rad_pkt = peap_instance.current_rad_pkt; ++ peap_instance.current_rad_pkt = req; ++ req = compose_eap_packet(req, tls_data, tls_data_length, FALSE); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ free(req); ++ return (-1); ++ } ++ client_state = peap_instance.client_state = (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_PEAP_IDENTITY); ++ break; ++ case (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_PEAP_IDENTITY): ++ peap_data = process_peap_message(&peap_data_length); ++ if (0 == peap_data) { ++ peap_log_debug("handle_req_rep: Error while extracting PEAP data: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ if (PW_EAP_IDENTITY != *peap_data) { ++ peap_log_debug("handle_req_rep: No PEAP Identity request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ tls_data_length = 0; ++ tls_data = compose_peap_identity(&tls_data_length); ++ if (0 == tls_data) { ++ peap_log_debug("handle_req_rep: Error while extracting TLS data: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ free(tls_data); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ req = compose_eap_packet(req, tls_data, tls_data_length, FALSE); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ free(req); ++ free(tls_data); ++ return (-1); ++ } ++ client_state = peap_instance.client_state = (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_MSCHAP_RESPONSE); ++ break; ++ case (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_MSCHAP_RESPONSE): ++ peap_data = process_peap_message(&peap_data_length); ++ if (0 == peap_data) { ++ peap_log_debug("handle_req_rep: Error while extracting PEAP data: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ if (peap_data != 0) { ++ peap_log_debug("handle_req_rep: peap_data = %x\n", *peap_data); ++ if ((PW_EAP_MSCHAPV2 != *peap_data) && (PW_EAP_REQUEST != *peap_data)) { ++ peap_log_debug("handle_req_rep: No PEAP MSCHAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ } ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ peap_data_length = 0; ++ /* ++ * If server response does not have MSCHAPV2 in PEAP data ++ * client has to send NAK response with the supported protocol ++ * details to server. Server on receiving NAK packet, it responds ++ * with its supported protocol. ++ */ ++ if (PW_EAP_MSCHAPV2 == *peap_data) { ++ peap_data = compose_mschap_response(&peap_data_length); ++ peap_log_debug("handle_req_rep: Compose MSCHAPv2 response of peap_data_length = %d\n", ++ peap_data_length); ++ client_state = peap_instance.client_state = (CLIENT_TLS_ESTABLISHED | CLIENT_RECEIVE_AUTH_RESULT); ++ } else if (PW_EAP_REQUEST == *peap_data) { ++ peap_data = compose_nak_respone(&peap_data_length); ++ peap_log_debug("handle_req_rep: Compose NAK response of peap_data_length = %d\n", ++ peap_data_length); ++ client_state = peap_instance.client_state = (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_NAK_RESPONSE); ++ } ++ ++ if (0 == peap_data) { ++ peap_log_debug("handle_req_rep: PEAP data compose failed "); ++ return -1; ++ } ++ ++ tls_data = get_tls_data(peap_data, peap_data_length, &tls_data_length); ++ if(0 == tls_data) { ++ peap_log_debug("handle_req_rep: Error get_tls_data \n"); ++ free(req); ++ free(peap_data); ++ return (-1); ++ } ++ peap_log_debug("handle_req_rep: get_tls_data returned success\n"); ++ free(peap_data); ++ peap_data = 0; ++ ++ req = compose_eap_packet(req, tls_data, tls_data_length, FALSE); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ free(req); ++ return (-1); ++ } ++ peap_log_debug("handle_req_rep: compose_eap_packet returned success\n"); ++ peap_log_debug("handle_req_rep: get_tls_data returned success\n"); ++ peap_log_debug("handle_req_rep: compose_eap_packet returned success\n"); ++ peap_log_debug("handle_req_rep: Client State = 0x%x\n", client_state); ++ break; ++ case (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_NAK_RESPONSE): ++ client_state = peap_instance.client_state = (CLIENT_TLS_ESTABLISHED | CLIENT_SEND_MSCHAP_RESPONSE); ++ peap_log_debug("handle_req_rep: Client State = 0x%x\n", client_state); ++ break; ++ case (CLIENT_TLS_ESTABLISHED | CLIENT_RECEIVE_AUTH_RESULT): ++ peap_data = process_peap_message(&peap_data_length); ++ if (0 == peap_data) { ++ peap_log_debug("handle_req_rep: Error while extracting PEAP data: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ if (PW_EAP_MSCHAPV2 != *peap_data) { ++ peap_log_debug("handle_req_rep: No PEAP MSCHAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ free(req); ++ req = 0; ++ req = rad_alloc(RAD_NEW_VECTOR); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while allocating req: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ compose_radius_pkt(req, PW_AUTHENTICATION_REQUEST); ++ if (PW_EAP_MSCHAPV2_SUCCESS != *(peap_data + 1)) { ++ peap_log_debug("handle_req_rep:MSCHAPV2 authentication failure !!!!!!\n"); ++ return(-1); ++ ++ /* Probably we need to take these error checks later into account*/ ++ ++ /* ++ * return -1 ; ++ */ ++ /* ++ * Check the failure reason ++ */ ++ /* ++ * error_code = get_mschapv2_error(peap_data); ++ */ ++ /* ++ * E=646 ERROR_RESTRICTED_LOGON_HOURS ++ */ ++ ++ /* ++ * E=647 ERROR_ACCT_DISABLED ++ */ ++ ++ /* ++ * E=648 ERROR_PASSWD_EXPIRED ++ */ ++ /* ++ * compose_mschap_changepwd_request(); ++ */ ++ ++ /* ++ * E=649 ERROR_NO_DIALIN_PERMISSION ++ */ ++ ++ /* ++ * E=691 ERROR_AUTHENTICATION_FAILURE ++ */ ++ /* ++ * if (is_mschapv2_failure_retryable()) { ++ * compose_mschapv2_response(); }else{ ++ * compose_mschapv2_failure_response(); } ++ */ ++ ++ /* ++ * E=709 ERROR_CHANGING_PASSWORD ++ */ ++ ++ } else {/*SUCCESS*/ ++ peap_data_length = 0; ++ peap_data = compose_mschap_response(&peap_data_length); ++ if (0 == peap_data) { ++ peap_log_debug("handle_req_rep: compose_mschap_response failed "); ++ free(req); ++ return -1; ++ } ++ } ++ tls_data_length = 0; ++ tls_data = get_tls_data(peap_data, peap_data_length, &tls_data_length); ++ if(0 == tls_data) { ++ peap_log_debug("handle_req_rep: Error get_tls_data \n"); ++ free(req); ++ free(peap_data); ++ return (-1); ++ } ++ free(peap_data); ++ peap_data = 0; ++ req = compose_eap_packet(req, tls_data, tls_data_length, FALSE); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ free(req); ++ free(peap_data); ++ return (-1); ++ } ++ peap_data_length = 0; ++ client_state = peap_instance.client_state = ++ (CLIENT_TLS_ESTABLISHED | CLIENT_RECEIVE_FINAL_CHALLENGE); ++ break ; ++ case (CLIENT_TLS_ESTABLISHED | CLIENT_RECEIVE_FINAL_CHALLENGE): ++ peap_data_length = 0; ++ peap_data = 0; ++ peap_data = process_peap_message(&peap_data_length); ++ if (0 == peap_data) { ++ peap_log_debug("handle_req_rep: Error while extracting PEAP data: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state (peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ ++ temp_len = sizeof(result_avp); ++ temp_len = htons(temp_len); ++ result_avp[0] = PW_EAP_RESPONSE; /* Response */ ++ result_avp[1] = *((uint8_t*)(peap_data + 1));/* Id from previous packet */ ++ (*(uint16_t*)(result_avp + 2)) = temp_len;/* Byte ordred length */ ++ ++ result_avp[4] = PW_EAP_EXTENTIONS;/* EAP Extension method */ ++ result_avp[5] = EAP_LENGTH_INCLUDED;/*Length included */ ++ result_avp[6] = RESULT_AVP;/* Result AVP */ ++ ++ (*(uint16_t*)(result_avp + 7)) = htons(RESULT_AVP_LEN); ++ /* ++ * Byte ordred length ++ * 7th and 8th Bytes ++ */ ++ ++ (*(uint16_t*)(result_avp + 9)) = htons(RESULT_AVP_RESULT); ++ /* Byte ordred result value ++ * 9th and 10th Bytes ++ */ ++ peap_data_length = sizeof(result_avp); ++ ++ tls_data_length = 0; ++ ++ tls_data = get_tls_data(result_avp, peap_data_length,&tls_data_length); ++ if(0 == tls_data) { ++ peap_log_debug("handle_req_rep: Error get_tls_data \n"); ++ free(req); ++ req = 0; ++ free(peap_data); ++ peap_data = 0 ; ++ return (-1); ++ } ++ peap_data = 0; ++ req = compose_eap_packet(req, tls_data, tls_data_length, FALSE); ++ if (0 == req) { ++ peap_log_debug("handle_req_rep: Error while composing EAP request: state 0x%x \n", ++ client_state); ++ peap_log_debug("handle_req_rep: Client state 0x%x, SSL state 0x%x \n", ++ client_state, SSL_get_state(peap_instance.tls_session->ssl)); ++ return (-1); ++ } ++ peap_data_length = 0; ++ client_state = peap_instance.client_state = ++ (CLIENT_TLS_ESTABLISHED | CLIENT_TLS_SHUTDOWN); ++ ++ break; ++ default: ++ peap_log_debug("handle_req_rep: Invalid client state 0x%x \n", client_state); ++ return (-1); ++ } /* end of switch */ ++ if (exit == TRUE) { ++ return ret; ++ } ++ ++ ret = rad_peap_send(req, NULL, peap_instance.shared_secret); ++ ++ if (ret < 0) { ++ peap_log_debug("handle_req_rep: Error while sending radius req: state 0x%x %s \n", ++ client_state, strerror(errno)); ++ if(req->data) free(req->data); ++ if(req) free(req); ++ return (-1); ++ } ++ ++ peap_log_debug("handle_req_rep: after rad_peap_send \n"); ++ ++ retval = wait_for_server_response(); ++ if (retval < 0) { ++ peap_log_debug("handle_req_rep:Error in getting server entry \n"); ++ return -1; ++ } ++ ++ if (0 != retval) { ++ if (PEAP_SERVER_RETRY == retval) { ++ peap_log_debug("handle_req_rep: Retrying the request \n"); ++ return PEAP_SERVER_RETRY; ++ } else if(PAM_AUTHINFO_UNAVAIL == retval) { ++ peap_log_debug("handle_req_rep: Server failed to respond\n"); ++ return PAM_AUTHINFO_UNAVAIL; ++ } ++ ++ } ++ /* ++ * The radius server responded. We should go ahead with ++ * receiving the data ++ */ ++ peap_log_debug("handle_req_rep: calling rad_peap_recv \n"); ++ ++ peap_instance.server_rep = rep = rad_peap_recv(); ++ if (0 == rep) { ++ peap_log_debug("handle_req_rep: Error while receiving radius req 0x%x \n", ++ client_state); ++ if(req->data) free(req->data); ++ if(req) free(req); ++ return (-1); ++ } ++ peap_log_debug("handle_req_rep: calling process_rad_rep \n"); ++ ret = process_rad_rep(); ++ if (ret < 0) { ++ peap_log_debug("handle_req_rep: Error while processing radius response: state 0x%x \n", ++ client_state); ++ if(req->data) free(req->data); ++ if(req) free(req); ++ return (-1); ++ } ++ peap_log_debug("handle_req_rep: calling extract_eap_message \n"); ++ ret = extract_eap_message(); ++ if (ret < 0) { ++ peap_log_debug("handle_req_rep: Error while processing EAP message: state 0x%x \n", ++ client_state); ++ if(req->data) free(req->data); ++ if(req) free(req); ++ return (-1); ++ } ++ if((CLIENT_TLS_ESTABLISHED | CLIENT_TLS_SHUTDOWN) == client_state) { ++ exit=TRUE; ++ } ++ peap_log_debug("handle_req_rep: after calling extract_eap_message \n"); ++ } while (FALSE == exit); ++ ++ return (ret); ++} ++ ++ ++/* ++ *int setup_client ++ * ++ * Description ++ * This function sets up the global variable values ++ * specific to a user and a server. ++ * Input ++ * const char * user_name, const char * user_password, radius_conf_t* conf ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Retuns Success or Failure ++ */ ++static int ++setup_client(const char * user_name, const char * user_password, radius_conf_t * conf) ++{ ++ int vrf_id, retVal; ++ ++#undef CLEAN_UP ++#define CLEAN_UP() \ ++ do { \ ++ if(peap_instance.shared_secret){ \ ++ free(peap_instance.shared_secret); \ ++ peap_instance.shared_secret = 0; \ ++ } \ ++ if(peap_instance.user_name) { \ ++ free(peap_instance.user_name); \ ++ peap_instance.user_name = 0; \ ++ } \ ++ } while (0); ++ ++ radius_server_t * cur_serv = conf->server; ++ ++ if (0 == cur_serv) { ++ peap_log_debug("setup_client:No server info available"); ++ return -1 ; ++ } ++ if (strlen(user_name) >= MAX_NT_USERNAME) { ++ peap_log_debug("setup_client:Username length is greater than allowed"); ++ return -1 ; ++ } ++ if (strlen(user_password) >= MAX_NT_PASSWORD) { ++ peap_log_debug("setup_client:Password length is greater than allowed"); ++ return -1 ; ++ } ++ if(strlen(cur_serv->secret) >= MAX_SHARED_SEC_LEN){ ++ peap_log_debug("setup_client:Shared secret length is greater than allowed"); ++ return -1 ; ++ } ++ peap_instance.sockfd = (((cur_serv->ip->sa_family) == AF_INET6) ? ++ (cur_serv->sockfd6 != -1 ? cur_serv->sockfd6 : conf->sockfd6) : ++ (cur_serv->sockfd != -1 ? cur_serv->sockfd : conf->sockfd)); ++ ++ peap_instance.retries = conf->retries + 1; ++ ++ ++#if 0 ++ switch(cur_serv->ip->sa_family) { ++ case AF_INET: ++ peap_instance.dst_port = ++ ntohs(((struct sockaddr_in *) cur_serv->ip)->sin_port); ++ peap_instance.dst_ipaddr = ++ (uint32_t) ((((struct sockaddr_in *)(cur_serv->ip))->sin_addr).s_addr); ++ break; ++ case AF_INET6: ++ peap_instance.dst_port = ++ ntohs(((struct sockaddr_in6 *) cur_serv->ip)->sin6_port); ++ peap_instance.dst_ipaddr = 0; ++#if 0 /* To Do IPV6, Use PW_NAS_IPV6_ADDRESS attribute */ ++ peap_instance.dst_ip6addr = ++ (((struct sockaddr_in6 *)(cur_serv->ip))->sin6_addr); ++#endif ++ break; ++ default: ++ peap_instance.dst_port = 0; ++ peap_instance.dst_ipaddr = 0; ++ break; ++ } ++#else ++ ++ peap_instance.dst_addr = cur_serv->ip; ++ ++#endif ++ ++ peap_instance.shared_secret = calloc(strlen(cur_serv->secret) + 1, sizeof(uint8_t)); ++ if (0 == peap_instance.shared_secret) { ++ peap_log_debug("setup_client: Memory allocation failed for shared_secret"); ++ return -1; ++ } ++ memcpy((char *) peap_instance.shared_secret, cur_serv->secret, strlen(cur_serv->secret)); ++ peap_instance.shared_secret[strlen(cur_serv->secret)] = '\0'; ++ ++ peap_instance.user_name = calloc(strlen(user_name) + 1, sizeof(uint8_t)); ++ if (0 == peap_instance.user_name) { ++ peap_log_debug("setup_client: Memory allocation failed for user_name"); ++ CLEAN_UP(); ++ return -1; ++ } ++ memcpy((char *) peap_instance.user_name, user_name, strlen(user_name)); ++ peap_instance.user_name[strlen(user_name)] = '\0'; ++ ++ ++ peap_instance.user_passwd = calloc(strlen(user_password) + 1, sizeof(uint8_t)); ++ if (0 == peap_instance.user_passwd) { ++ peap_log_debug("setup_client: Memory allocation failed for user_passwd"); ++ CLEAN_UP(); ++ return -1; ++ } ++ memcpy((char *) peap_instance.user_passwd, user_password, strlen(user_password)); ++ peap_instance.user_passwd[strlen(user_password)] = '\0'; ++ ++ peap_instance.client_state = CLIENT_INIT_STATE; ++ ++ return 0; ++} ++/* ++ * int pam_peap_authenticate ++ * ++ * Description ++ * This function sets up the global variable values ++ * specific to a user and a server and initialises ++ * the state machine to CLIENT_INIT_STATE; ++ * Input ++ * const char * user_name, const char * user_password, ++ * radius_conf_t * conf, AUTH_HDR *response ++ * ++ * Output ++ * None ++ * ++ * Return ++ * Returns Success or Failure or PAM_AUTHINFO_UNAVAIL when server ++ * is not reachable. ++ */ ++int ++pam_peap_authenticate(const char * user_name, const char * user_password, ++ radius_conf_t* conf, AUTH_HDR *response) ++{ ++ int ret = 0; ++ int found_server = -1; ++ RADIUS_PACKET * rep = 0; ++ uint8_t * radius_pkt = 0; ++ uint16_t rad_pkt_len = 0; ++ peap_instance.cur_serv = conf->server; ++ peap_instance.dst_addr = conf->server->ip; ++ ++ ret = setup_client(user_name, user_password, conf); ++ if (0 != ret) { ++ peap_log_debug("pam_peap_authenticate: setup_client error \n"); ++ return 7; ++ } ++ do { ++ peap_instance.client_state = CLIENT_INIT_STATE; ++ ret = handle_req_rep(); ++ if (0 == ret) { ++ peap_log_debug("pam_peap_authenticate: Authentication succeeded" ++ " Getting vendor specific attributes \n"); ++ ++ free(peap_instance.shared_secret); ++ free(peap_instance.user_name); ++ free(peap_instance.user_passwd); ++ free(peap_instance.tls_session); ++ rep = peap_instance.server_rep; ++ radius_pkt = rep->data; ++ memcpy(&rad_pkt_len, radius_pkt + 2, sizeof(uint16_t)); ++ /* Entire radius packet length */ ++ ++ rad_pkt_len = htons(rad_pkt_len); ++ ++ peap_log_debug("pam_peap_authenticate: rad_pkt_len is %d \n", rad_pkt_len); ++ peap_log_debug("pam_peap_authenticate: Copying the response \n"); ++ memcpy(response, radius_pkt, rad_pkt_len); ++ ++ found_server = 0; ++ break; ++ } ++ ++ } while (PEAP_SERVER_RETRY == ret); ++ ++ if(PAM_AUTHINFO_UNAVAIL == ret) { ++ peap_log_debug("\npam_peap_authenticate:Authentication Server failed to respond\n"); ++ return PAM_AUTHINFO_UNAVAIL; ++ } ++ if (0 != found_server) { ++ peap_log_debug("\npam_peap_authenticate:Authentication Failed : Exiting ...\n"); ++ if (ret == PAM_AUTH_SECUREID) { ++ peap_log_debug("\npam_peap_authenticate:ret %d\n", ret); ++ return (PAM_AUTH_SECUREID); ++ } ++ return -1; ++ } ++ else { ++ peap_log_debug("\npam_peap_authenticate:Authenticated Successfully > Bye...\n"); ++ return 0; ++ } ++} ++ +Index: pam_radius/Makefile +=================================================================== +--- pam_radius.orig/Makefile ++++ pam_radius/Makefile +@@ -7,6 +7,14 @@ + ############################################################################# + VERSION=1.4.0 + ++# FreeRADIUS Dependencies for PEAP-MSCHAPV2 ++RADIUSPATH = ../freeradius/freeradius-server/ ++RADIUSLIBPATH = $(RADIUSPATH)/src/lib/.libs/ ++RADIUSEAPLIBPATH = $(RADIUSPATH)/src/modules/rlm_eap/libeap/.libs/ ++RADIUSINCPATH = ../$(RADIUSPATH)/src/include/ ++RADIUSEAPINCPATH = ../$(RADIUSPATH)/src/modules/rlm_eap/ ++RADIUSEAPLIBINCPATH = ../$(RADIUSPATH)/src/modules/rlm_eap/libeap/ ++ + ###################################################################### + # + # If we're really paranoid, use these flags +@@ -15,6 +23,8 @@ VERSION=1.4.0 + # If you're not using GCC, then you'll have to change the CFLAGS. + # + CFLAGS += -Wall -fPIC ++CFLAGS += -I$(RADIUSEAPINCPATH) -I$(RADIUSEAPLIBINCPATH) -I$(RADIUSINCPATH) ++export CFLAGS + + # + # On Irix, use this with MIPSPRo C Compiler, and don't forget to export CC=cc +@@ -25,6 +35,7 @@ CFLAGS += -Wall -fPIC + + #LDFLAGS += -shared -Wl,--version-script=pamsymbols.ver + LDFLAGS += -shared ++LDFLAGS += -L$(RADIUSLIBPATH) -L$(RADIUSEAPLIBPATH) + + $(if $(wildcard src/config.h),,$(error You must run './configure [options]' before doing 'make')) + +@@ -46,6 +57,12 @@ src/pam_radius_auth.o: src/pam_radius_au + src/md5.o: src/md5.c src/md5.h + @$(MAKE) -C src $(notdir $@) + ++src/mschapv2.o: src/mschapv2.c ++ @$(MAKE) -C src $(notdir $@) ++ ++src/radpeapclient.o: src/radpeapclient.c src/radpeapclient.h ++ @$(MAKE) -C src $(notdir $@) ++ + # + # This is what should work on Irix: + #pam_radius_auth.so: pam_radius_auth.o md5.o +@@ -64,8 +81,8 @@ src/md5.o: src/md5.c src/md5.h + # + # gcc -shared pam_radius_auth.o md5.o -lpam -lc -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 ++pam_radius_auth.so: src/pam_radius_auth.o src/md5.o src/mschapv2.o src/radpeapclient.o ++ $(CC) $(LDFLAGS) $^ -lpam -lradius-1.1.8 -leap-1.1.8 -lssl -o pam_radius_auth.so + + ###################################################################### + # +@@ -83,4 +100,4 @@ dist: + # + .PHONY: clean + clean: +- @rm -f *~ *.so *.o src/*.o src/*~ ++ @rm -f *~ *.so *.o src/*.o src/*~ build-stamp +Index: pam_radius/src/Makefile +=================================================================== +--- pam_radius.orig/src/Makefile ++++ pam_radius/src/Makefile +@@ -6,6 +6,13 @@ pam_radius_auth.o: pam_radius_auth.c pam + md5.o: md5.c md5.h + $(CC) $(CFLAGS) -c $< -o $@ + ++mschapv2.o: mschapv2.c ++ $(CC) $(CFLAGS) -c $< -o $@ ++ ++radpeapclient.o: radpeapclient.c radpeapclient.h ++ $(CC) $(CFLAGS) -c $< -o $@ ++ ++ + .PHONY: clean + clean: + @rm -f *~ *.so *.o src/*.o src/*~ diff --git a/src/radius/pam/patches/0003-nas-ip-address-config.patch b/src/radius/pam/patches/0003-nas-ip-address-config.patch new file mode 100644 index 000000000000..dbbf728c376d --- /dev/null +++ b/src/radius/pam/patches/0003-nas-ip-address-config.patch @@ -0,0 +1,943 @@ +Index: pam_radius/src/pam_radius_auth.c +=================================================================== +--- pam_radius.orig/src/pam_radius_auth.c ++++ pam_radius/src/pam_radius_auth.c +@@ -37,6 +37,7 @@ + #define PAM_SM_SESSION + + #include "pam_radius_auth.h" ++#include "pam_radius_stats.h" + + #define DPRINT if (debug) _pam_log + +@@ -148,7 +149,54 @@ static int _pam_parse(int argc, CONST ch + } else { + _pam_log(LOG_WARNING, "ignoring '%s'", *argv); + } ++ } else if (!strncmp(*argv, "nas_ip_address=", 15)) { ++ /* Convert it sockaddr. ++ */ ++ struct addrinfo hints; ++ struct addrinfo *ai_start; ++ struct addrinfo *ai; ++ int r; ++ ++ memset(&hints, 0, sizeof(hints)); ++ hints.ai_family = AF_UNSPEC; ++ hints.ai_socktype = SOCK_DGRAM; ++ hints.ai_flags = AI_ADDRCONFIG|AI_NUMERICHOST; ++ ++ r = getaddrinfo(*argv+15, NULL, &hints, &ai_start); ++ if (r != 0) { ++ _pam_log(LOG_WARNING, "getaddrinfo() fails. ignoring '%s'", *argv); ++ } else { ++ ++ ai = ai_start; ++ while (ai != NULL) { ++ if (ai->ai_family == AF_INET) { ++ memcpy(&(conf->client_ip_storage), ((struct sockaddr_in *)ai->ai_addr), ++ sizeof(struct sockaddr_in)); ++ conf->client_ip = (struct sockaddr *) &(conf->client_ip_storage); ++ break; ++ } else if (ai->ai_family == AF_INET6) { ++ memcpy(&(conf->client_ip_storage), ((struct sockaddr_in6 *)ai->ai_addr), ++ sizeof(struct sockaddr_in6)); ++ conf->client_ip = (struct sockaddr *) &(conf->client_ip_storage); ++ break; ++ } ++ } ++ ++ if (!(conf->client_ip)) ++ _pam_log(LOG_WARNING, "Invalid address. ignoring '%s'", *argv); ++ } ++ + ++ } else if (!strncmp(*argv, "statistics=", 11)) { ++ /* Verify filename doesn't contain '/'. ++ */ ++ if (!strchr(*argv+11, '/')) ++ conf->statistics = *argv+11; ++ else ++ _pam_log(LOG_WARNING, "'/' present, ignoring '%s'", *argv); ++ ++ } else if (!strncmp(*argv, "trace", 5)) { ++ conf->trace = "pam_radius_trace.log"; + } else { + _pam_log(LOG_WARNING, "unrecognized option '%s'", *argv); + } +@@ -424,13 +472,30 @@ static void add_int_attribute(AUTH_HDR * + add_attribute(request, type, (unsigned char *) &value, sizeof(int)); + } + +-static void add_nas_ip_address(AUTH_HDR *request, char *hostname) { ++static void add_nas_ip_address(AUTH_HDR *request, char *hostname, radius_conf_t * conf) { + struct addrinfo hints; + struct addrinfo *ai_start; + struct addrinfo *ai; + int v4seen = 0, v6seen = 0; + int r; + ++ /* If there is a client_ip configured/discovered, use it instead. ++ */ ++ if (conf->client_ip) { ++ if ((conf->client_ip)->sa_family == AF_INET) { ++ r = ((struct sockaddr_in *)(conf->client_ip))->sin_addr.s_addr; ++ add_int_attribute(request, PW_NAS_IP_ADDRESS, ntohl(r)); ++ v4seen = 1; ++ } else if ((conf->client_ip)->sa_family == AF_INET6) { ++ add_attribute(request, PW_NAS_IPV6_ADDRESS, ++ (unsigned char *) &(((struct sockaddr_in6 *)conf->client_ip)->sin6_addr), 16); ++ v6seen = 1; ++ } ++ } ++ ++ if (v4seen || v6seen) ++ return; ++ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; +@@ -446,16 +511,25 @@ static void add_nas_ip_address(AUTH_HDR + v4seen = 1; + r = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr; + add_int_attribute(request, PW_NAS_IP_ADDRESS, ntohl(r)); ++ memcpy(&(conf->client_ip_storage), ((struct sockaddr_in *)ai->ai_addr), ++ sizeof(struct sockaddr_in)); ++ conf->client_ip = (struct sockaddr *) &(conf->client_ip_storage); + } + 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); ++ memcpy(&(conf->client_ip_storage), ((struct sockaddr_in6 *)ai->ai_addr), ++ sizeof(struct sockaddr_in6)); ++ conf->client_ip = (struct sockaddr *) &(conf->client_ip_storage); + } + ai = ai->ai_next; + } + + freeaddrinfo(ai_start); ++ ++ if (!conf->client_ip) ++ conf->client_ip = (struct sockaddr *)(&(conf->client_ip_storage)); + } + + /* +@@ -893,7 +967,7 @@ static void build_radius_packet(AUTH_HDR + } + + /* Perhaps add NAS IP Address (and v6 version) */ +- add_nas_ip_address(request, hostname); ++ add_nas_ip_address(request, hostname, conf); + + /* There's always a NAS identifier */ + if (conf->client_id && *conf->client_id) { +@@ -932,6 +1006,8 @@ static int talk_radius(radius_conf_t *co + int retval; + int sockfd; + socklen_t salen; ++ struct sockaddr_storage saremote; ++ socklen_t saremotelen; + + /* ************************************************************ */ + /* Now that we're done building the request, we can send it */ +@@ -1026,12 +1102,16 @@ static int talk_radius(radius_conf_t *co + total_length = ntohs(request->length); + server_tries = tries; + send: ++ _pam_radius_incr( conf, request->code); + if (server->ip->sa_family == AF_INET) { + salen = sizeof(struct sockaddr_in); + } else { + salen = sizeof(struct sockaddr_in6); + } + ++ _pam_radius_trace( conf, TRUE, server->ip, ++ (unsigned char *) request, total_length); ++ + /* send the packet */ + if (sendto(sockfd, (char *) request, total_length, 0, + server->ip, salen) < 0) { +@@ -1072,9 +1152,11 @@ static int talk_radius(radius_conf_t *co + if (rcode == 0) { + _pam_log(LOG_ERR, "RADIUS server %s failed to respond", server->hostname); + if (--server_tries) { ++ _pam_radius_incr( conf, STATS_RETRY(request->code)); + goto send; + } + ok = FALSE; ++ _pam_radius_incr( conf, STATS_TIMEOUT); + break; /* exit from the loop */ + } else if (rcode < 0) { + +@@ -1111,8 +1193,10 @@ static int talk_radius(radius_conf_t *co + #endif + + /* try to receive some data */ ++ memset((char *) &saremote, 0, sizeof(saremote)); ++ saremotelen = sizeof(saremote); + if ((total_length = recvfrom(sockfd, (void *) response, BUFFER_SIZE, +- 0, NULL, NULL)) < 0) { ++ 0, (struct sockaddr *) &saremote, &saremotelen)) < 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", +@@ -1122,6 +1206,10 @@ static int talk_radius(radius_conf_t *co + + /* there's data, see if it's valid */ + } else { ++ ++ _pam_radius_trace( conf, FALSE, (struct sockaddr *) &saremote, ++ (unsigned char *) response, total_length); ++ + char *p = server->secret; + + if ((ntohs(response->length) != total_length) || +@@ -1129,6 +1217,7 @@ static int talk_radius(radius_conf_t *co + _pam_log(LOG_ERR, "RADIUS packet from server %s is corrupted", + server->hostname); + ok = FALSE; ++ _pam_radius_incr( conf, STATS_INVALID_PACKET); + break; + } + +@@ -1155,6 +1244,7 @@ static int talk_radius(radius_conf_t *co + _pam_log(LOG_ERR, "packet from RADIUS server %s failed verification: " + "The shared secret is probably incorrect.", server->hostname); + ok = FALSE; ++ _pam_radius_incr( conf, STATS_BAD_AUTHENTICATOR); + break; + } + +@@ -1166,6 +1256,7 @@ static int talk_radius(radius_conf_t *co + "request packet ID %d: verification of packet fails", + response->id, request->id); + ok = FALSE; ++ _pam_radius_incr( conf, STATS_INVALID_PACKET); + break; + } + } +@@ -1418,6 +1509,8 @@ PAM_EXTERN int pam_sm_authenticate(pam_h + retval = talk_radius(&config, request, response, password, NULL, config.retries + 1); + PAM_FAIL_CHECK; + ++ _pam_radius_incr( &config, response->code); ++ + DPRINT(LOG_DEBUG, "Got RADIUS response code %d", response->code); + + /* +@@ -1485,6 +1578,8 @@ PAM_EXTERN int pam_sm_authenticate(pam_h + retval = talk_radius(&config, request, response, resp2challenge, NULL, 1); + PAM_FAIL_CHECK; + ++ _pam_radius_incr( &config, response->code); ++ + DPRINT(LOG_DEBUG, "Got response to challenge code %d", response->code); + + /* +@@ -1677,6 +1772,8 @@ static int pam_private_session(pam_handl + retval = talk_radius(&config, request, response, NULL, NULL, 1); + PAM_FAIL_CHECK; + ++ _pam_radius_incr( &config, response->code); ++ + /* oops! They don't have the right password. Complain and die. */ + if (response->code != PW_ACCOUNTING_RESPONSE) { + retval = PAM_PERM_DENIED; +@@ -1792,6 +1889,8 @@ PAM_EXTERN int pam_sm_chauthtok(pam_hand + retval = talk_radius(&config, request, response, password, NULL, 1); + PAM_FAIL_CHECK; + ++ _pam_radius_incr( &config, response->code); ++ + /* oops! They don't have the right password. Complain and die. */ + if (response->code != PW_AUTHENTICATION_ACK) { + _pam_forget(password); +@@ -1895,6 +1994,8 @@ PAM_EXTERN int pam_sm_chauthtok(pam_hand + retval = talk_radius(&config, request, response, new_password, password, 1); + PAM_FAIL_CHECK; + ++ _pam_radius_incr( &config, response->code); ++ + /* Whew! Done password changing, check for password acknowledge */ + if (response->code != PW_PASSWORD_ACK) { + retval = PAM_AUTHTOK_ERR; +Index: pam_radius/src/pam_radius_auth.h +=================================================================== +--- pam_radius.orig/src/pam_radius_auth.h ++++ pam_radius/src/pam_radius_auth.h +@@ -160,6 +160,8 @@ typedef struct radius_conf_t { + int retries; + int localifdown; + char *client_id; ++ struct sockaddr_storage client_ip_storage; ++ struct sockaddr *client_ip; + int accounting_bug; + int force_prompt; + int max_challenge; +@@ -171,6 +173,9 @@ typedef struct radius_conf_t { + int prompt_attribute; + int privilege_level; + int auth_type; ++#define MAXFILENAME 128 ++ CONST char *trace; /* Packet Trace File */ ++ CONST char *statistics; /* Statistics File */ + } radius_conf_t; + + #endif /* PAM_RADIUS_H */ +Index: pam_radius/src/radpeapclient.c +=================================================================== +--- pam_radius.orig/src/radpeapclient.c ++++ pam_radius/src/radpeapclient.c +@@ -31,6 +31,7 @@ + #include "radpeapclient.h" + // #include "mschapv2.h" + // #include "pam_radius_auth.h" ++#include "pam_radius_stats.h" + + static unsigned char cached_auth_challenge[16] = { 0 }; + static unsigned char cached_peer_challenge[16] = { 0 }; +@@ -324,7 +325,7 @@ compose_eap_identity_pkt(RADIUS_PACKET * + /* + * NAS IP address, Remote Access policy dictates this to be present + */ +- switch ((peap_instance.dst_addr)->sa_family) { ++ switch ((peap_instance.client_ip)->sa_family) { + + case AF_INET: + +@@ -340,7 +341,7 @@ compose_eap_identity_pkt(RADIUS_PACKET * + } + nas_tlv->type = PW_NAS_IP_ADDRESS; + nas_tlv->length = nas_tlv_size; +- nas_ip_addr = ((struct sockaddr_in *)peap_instance.dst_addr)->sin_addr.s_addr; ++ nas_ip_addr = ((struct sockaddr_in *)peap_instance.client_ip)->sin_addr.s_addr; + memcpy(nas_tlv->data, (uint32_t *) & nas_ip_addr, sizeof(uint32_t)); + break; + +@@ -358,7 +359,7 @@ compose_eap_identity_pkt(RADIUS_PACKET * + } + nas_tlv->type = PW_NAS_IPV6_ADDRESS; + nas_tlv->length = nas_tlv_size; +- nas_ipv6_addr = &(((struct sockaddr_in6 *)peap_instance.dst_addr)->sin6_addr.s6_addr); ++ nas_ipv6_addr = (uint8_t *) &(((struct sockaddr_in6 *)peap_instance.client_ip)->sin6_addr.s6_addr); + memcpy(nas_tlv->data, nas_ipv6_addr, IPV6_ADDR_SIZE); + break; + +@@ -729,7 +730,7 @@ compose_eap_packet(RADIUS_PACKET * req, + * NAS IP address, it is need in all packets as + * W2K3 server Remote Access policy is based on this + */ +- switch ((peap_instance.dst_addr)->sa_family) { ++ switch ((peap_instance.client_ip)->sa_family) { + + case AF_INET: + +@@ -741,11 +742,11 @@ compose_eap_packet(RADIUS_PACKET * req, + peap_log_debug( + "compose_eap_packet:Memory allocation failed,eap_msg is NULL\n"); + CLEAN_UP(); +- return -1; ++ return 0; + } + nas_tlv->type = PW_NAS_IP_ADDRESS; + nas_tlv->length = nas_tlv_size; +- nas_ip_addr = ((struct sockaddr_in *)peap_instance.dst_addr)->sin_addr.s_addr; ++ nas_ip_addr = ((struct sockaddr_in *)peap_instance.client_ip)->sin_addr.s_addr; + memcpy(nas_tlv->data, (uint32_t *) & nas_ip_addr, sizeof(uint32_t)); + break; + +@@ -759,11 +760,11 @@ compose_eap_packet(RADIUS_PACKET * req, + peap_log_debug( + "compose_eap_packet:Memory allocation failed,eap_msg is NULL\n"); + CLEAN_UP(); +- return -1; ++ return 0; + } + nas_tlv->type = PW_NAS_IPV6_ADDRESS; + nas_tlv->length = nas_tlv_size; +- nas_ipv6_addr = &(((struct sockaddr_in6 *)peap_instance.dst_addr)->sin6_addr.s6_addr); ++ nas_ipv6_addr = (uint8_t *) &(((struct sockaddr_in6 *)peap_instance.client_ip)->sin6_addr.s6_addr); + memcpy(nas_tlv->data, nas_ipv6_addr, IPV6_ADDR_SIZE); + break; + +@@ -1141,6 +1142,11 @@ rad_peap_send(RADIUS_PACKET * packet, co + + sa = (struct sockaddr *) peap_instance.dst_addr; + ++ _pam_radius_trace( peap_instance.conf, TRUE, sa, ++ (unsigned char *) packet->data, (int) packet->data_len); ++ ++ _pam_radius_incr( peap_instance.conf, packet->code); ++ + #ifndef WITH_UDPFROMTO + return sendto(packet->sockfd, packet->data, (int) packet->data_len, 0, + (struct sockaddr *) sa, +@@ -1329,6 +1335,9 @@ static RADIUS_PACKET *rad_recv_ipv6(int + return NULL; + } + ++ _pam_radius_trace( peap_instance.conf, FALSE, (struct sockaddr *) &saremote, ++ (unsigned char *) data, (int) packet->data_len); ++ + /* + * Fill IP header fields. We need these for the error + * messages which may come later. +@@ -1369,6 +1378,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + peap_log( LOG_ERR, "Malformed RADIUS packet from host %s:" + " too short (received %d < minimum %d)", + remote_ipaddr, packet->data_len, AUTH_HDR_LEN); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1383,6 +1393,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " too long (received %d > maximum %d)", + remote_ipaddr, + packet->data_len, MAX_PACKET_LEN); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1405,6 +1416,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " unknown packet code %d", + remote_ipaddr, + hdr->code); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1425,6 +1437,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " too short (length %d < minimum %d)", + remote_ipaddr, + totallen, AUTH_HDR_LEN); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1441,6 +1454,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " too long (length %d > maximum %d)", + remote_ipaddr, + totallen, MAX_PACKET_LEN); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1458,6 +1472,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " received %d octets, packet length says %d", + remote_ipaddr, + packet->data_len, totallen); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1503,6 +1518,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + "Malformed RADIUS packet from host %s:" + " Invalid attribute 0", + remote_ipaddr); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1517,6 +1533,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " attribute %d too short", + remote_ipaddr, + attr[0]); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1539,6 +1556,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " Message-Authenticator has invalid length %d", + remote_ipaddr, + attr[1] - 2); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1567,6 +1585,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + "Malformed RADIUS packet from host %s:" + " packet attributes do NOT exactly fill the packet", + remote_ipaddr); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1583,6 +1602,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + " (received %d, max %d are allowed).", + remote_ipaddr, + num_attributes, librad_max_attributes); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1601,6 +1621,7 @@ static RADIUS_PACKET *rad_recv_ipv6(int + peap_log( LOG_ERR, "Insecure packet from host %s:" + " Received EAP-Message with no Message-Authenticator.", + remote_ipaddr); ++ _pam_radius_incr( peap_instance.conf, STATS_INVALID_PACKET); + free(packet); + return NULL; + } +@@ -1608,14 +1629,17 @@ static RADIUS_PACKET *rad_recv_ipv6(int + if ((hdr->code > 0) && (hdr->code < 52)) { + peap_log_debug("rad_recv: %s packet from host %s:%d", + packet_codes[hdr->code], +- remote_ipaddr); ++ remote_ipaddr, packet->src_port); + } else { + peap_log_debug("rad_recv: Packet from host %s:%d code=%d", +- remote_ipaddr, ++ remote_ipaddr, packet->src_port, + hdr->code); + } + peap_log_debug(", id=%d, length=%d\n", hdr->id, totallen); + ++ if (hdr->code != PW_AUTHENTICATION_ACK) ++ _pam_radius_incr( peap_instance.conf, hdr->code); ++ + /* + * Fill RADIUS header fields + */ +@@ -1665,6 +1689,7 @@ rad_peap_recv() + } + if ((rad_decode(rep, 0, peap_instance.shared_secret)) != 0) { + peap_log_debug("rad_peap_recv: rad_decode failed\n"); ++ _pam_radius_incr( peap_instance.conf, STATS_BAD_AUTHENTICATOR); + return 0; + } + /* +@@ -2472,8 +2497,10 @@ wait_for_server_response() + peap_log_debug("wait_for_server_response: Server timed out. peap_instance.retriesleft %d \n", + peap_instance.retries); + if (--(peap_instance.retries)) { ++ _pam_radius_incr( peap_instance.conf, STATS_RETRY(STATS_AUTH_REQUEST)); + return PEAP_SERVER_RETRY; + } else { ++ _pam_radius_incr( peap_instance.conf, STATS_TIMEOUT); + return PAM_AUTHINFO_UNAVAIL; + } + } +@@ -3203,6 +3230,11 @@ setup_client(const char * user_name, con + + peap_instance.retries = conf->retries + 1; + ++ /* ++ * Set up peap_instance.client_ip from client_ip. ++ */ ++ peap_instance.client_ip = conf->client_ip; ++ + + #if 0 + switch(cur_serv->ip->sa_family) { +@@ -3292,6 +3324,7 @@ pam_peap_authenticate(const char * user_ + uint16_t rad_pkt_len = 0; + peap_instance.cur_serv = conf->server; + peap_instance.dst_addr = conf->server->ip; ++ peap_instance.conf = conf; + + ret = setup_client(user_name, user_password, conf); + if (0 != ret) { +Index: pam_radius/src/radpeapclient.h +=================================================================== +--- pam_radius.orig/src/radpeapclient.h ++++ pam_radius/src/radpeapclient.h +@@ -300,6 +300,8 @@ typedef struct _tls_data_list { + struct _tls_data_list * list_end; + } tls_data_list_t; + ++struct radius_conf_t; ++ + /* + * The global data structure used for maintaining *all* the + * information needed at any point of time during the +@@ -312,6 +314,8 @@ typedef struct _peap_instance { + #endif + struct sockaddr *dst_addr; + struct sockaddr *src_addr; ++ struct sockaddr *client_ip; ++ struct radius_conf_t * conf; + int sockfd; + char * shared_secret; + char * user_name; +@@ -371,6 +375,8 @@ typedef struct radius_conf_t { + int retries; + int localifdown; + char *client_id; ++ struct sockaddr_storage client_ip_storage; ++ struct sockaddr *client_ip; + int accounting_bug; + int force_prompt; + int max_challenge; +@@ -383,6 +389,8 @@ typedef struct radius_conf_t { + int prompt_attribute; + int privilege_level; + int auth_type; ++ CONST char * trace; /* Packet Trace File */ ++ CONST char * statistics; /* Statistics File */ + } radius_conf_t; + + /* +Index: pam_radius/src/pam_radius_stats.c +=================================================================== +--- /dev/null ++++ pam_radius/src/pam_radius_stats.c +@@ -0,0 +1,109 @@ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pam_radius_auth.h" ++#include "pam_radius_stats.h" ++ ++void _pam_radius_incr(radius_conf_t * conf, unsigned int index) ++{ ++ unsigned long counters[STATS_MAX_COUNTER]; ++ char statistics[MAX_RADIUS_STATS_PATHNAME_LEN]; ++ FILE * fp; ++ int fd; ++ int retry_lock; ++ ++ if (!conf||!conf->statistics||!index||(index >= STATS_MAX_COUNTER)) { ++ return; ++ } ++ ++ /* Open the file. ++ */ ++ snprintf(statistics, sizeof(statistics), "%s/%s", STATISTICS_DIR, ++ conf->statistics); ++ statistics[sizeof(statistics) - 1] = 0; ++ if ((fp = fopen(statistics, "r+")) == NULL) { ++ return; ++ } ++ ++ /* Lock the file. ++ */ ++ fd = fileno(fp); ++ for (retry_lock = 3; retry_lock; retry_lock--) { ++ if (flock(fd, LOCK_EX|LOCK_NB) == 0) ++ break; ++ usleep(250*1000); /* quarter second */ ++ } ++ if (!retry_lock) { ++ return; ++ } ++ ++ /* Read ++ */ ++ memset((char *) (&(counters[0])), 0, sizeof(counters)); ++ fscanf(fp, ++ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " ++ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " ++ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " ++ "%ld %ld %ld %ld %ld %ld", ++ &(counters[0]), &(counters[1]), &(counters[2]), &(counters[3]), ++ &(counters[4]), &(counters[5]), &(counters[6]), &(counters[7]), ++ &(counters[8]), &(counters[9]), &(counters[10]), &(counters[11]), ++ &(counters[12]), &(counters[13]), &(counters[14]), &(counters[15]), ++ &(counters[16]), &(counters[17]), &(counters[18]), &(counters[19]), ++ &(counters[20]), &(counters[21]), &(counters[22]), &(counters[23]), ++ &(counters[24]), &(counters[25]), &(counters[26]), &(counters[27]), ++ &(counters[28]), &(counters[29]), &(counters[30]), &(counters[31]), ++ &(counters[32]), &(counters[33]), &(counters[34]), &(counters[35]) ++ ); ++ ++ /* Increment ++ */ ++ (counters[index])++; ++ ++ /* Write ++ */ ++ rewind(fp); ++ fprintf(fp, ++ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " ++ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " ++ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " ++ "%ld %ld %ld %ld %ld %ld", ++ (counters[0]), (counters[1]), (counters[2]), (counters[3]), ++ (counters[4]), (counters[5]), (counters[6]), (counters[7]), ++ (counters[8]), (counters[9]), (counters[10]), (counters[11]), ++ (counters[12]), (counters[13]), (counters[14]), (counters[15]), ++ (counters[16]), (counters[17]), (counters[18]), (counters[19]), ++ (counters[20]), (counters[21]), (counters[22]), (counters[23]), ++ (counters[24]), (counters[25]), (counters[26]), (counters[27]), ++ (counters[28]), (counters[29]), (counters[30]), (counters[31]), ++ (counters[32]), (counters[33]), (counters[34]), (counters[35]) ++ ); ++ ++#if 0 /* Should be released on close */ ++ flock(fd, LOCK_UN|LOCK_NB); ++#endif ++ ++ fclose(fp); ++ ++} ++ ++ ++#if defined(FOR_TESTING) ++ ++radius_conf_t conf; ++ ++int main(int ac, char * av[]) { ++ ++ conf.statistics = "global"; ++ ++ _pam_radius_incr(&conf, STATS_AUTH_REQUEST); ++ _pam_radius_incr(&conf, STATS_RETRY(STATS_AUTH_REQUEST)); ++ _pam_radius_incr(&conf, STATS_INVALID_PACKET); ++ ++} ++ ++#endif +Index: pam_radius/src/pam_radius_stats.h +=================================================================== +--- /dev/null ++++ pam_radius/src/pam_radius_stats.h +@@ -0,0 +1,43 @@ ++ ++ ++ ++#if defined(FOR_TESTING) ++ ++typedef struct radius_conf_t { ++ char * statistics; /* Statistics File */ ++ char * trace; /* Trace File */ ++} radius_conf_t; ++ ++#define STATISTICS_DIR "." ++#define TRACE_DIR "." ++ ++#endif ++ ++#ifndef STATISTICS_DIR ++#define STATISTICS_DIR "/etc/pam_radius_auth.d/statistics" ++#endif ++ ++#ifndef TRACE_DIR ++#define TRACE_DIR "/var/log" ++#endif ++ ++ ++#define STATS_RETRY(code) (16+code) ++ ++/* These are the same codes as in the RADIUS packet header */ ++#define STATS_AUTH_REQUEST 1 ++#define STATS_AUTH_ACK 2 ++#define STATS_AUTH_REJECT 3 ++ ++#define STATS_TIMEOUT 32 ++#define STATS_BAD_AUTHENTICATOR 33 ++#define STATS_INVALID_PACKET 34 ++#define STATS_MAX_COUNTER 36 /* Please change fscanf() fprintf() fmt */ ++ ++ ++#define MAX_RADIUS_STATS_PATHNAME_LEN 256 ++#define MAX_RADIUS_TRACE_PATHNAME_LEN 256 ++ ++void _pam_radius_incr(radius_conf_t * conf, unsigned int index); ++void _pam_radius_trace(radius_conf_t * conf, int send, ++ const struct sockaddr * addr, unsigned char * buf, int len); +Index: pam_radius/src/pam_radius_trace.c +=================================================================== +--- /dev/null ++++ pam_radius/src/pam_radius_trace.c +@@ -0,0 +1,156 @@ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#include "pam_radius_auth.h" ++#include "pam_radius_stats.h" ++ ++void _pam_radius_trace(radius_conf_t * conf, int send, ++ const struct sockaddr * addr, unsigned char * buf, int len) ++{ ++ FILE * fp; ++ char trace[MAX_RADIUS_TRACE_PATHNAME_LEN]; ++ int fd; ++ int retry_lock; ++#define CTIME_BUFSIZE (26+10) /* GNU library says 26 chars is enough */ ++ char ctime_buf[CTIME_BUFSIZE]; ++ time_t current_time = 0; ++ char ipVx[ INET6_ADDRSTRLEN + 1 ]; ++ int ct, idx; ++ unsigned int port = 0; ++ char port_str[sizeof(":65535")+1]; ++ ++ if (!conf->trace || (len < 0)) { ++ return; ++ } ++ ++ /* Open the file. ++ */ ++ snprintf(trace, sizeof(trace), "%s/%s", TRACE_DIR, conf->trace); ++ trace[sizeof(trace) - 1] = 0; ++ if ((fp = fopen(trace, "a")) == NULL) { ++ return; ++ } ++ ++ /* Lock the file. ++ */ ++ fd = fileno(fp); ++ for (retry_lock = 3; retry_lock; retry_lock--) { ++ if (flock(fd, LOCK_EX|LOCK_NB) == 0) ++ break; ++ usleep(250*1000); /* quarter second */ ++ } ++ if (!retry_lock) { ++ return; ++ } ++ ++ /* Current Time ++ */ ++ time(¤t_time); ++ ctime_buf[0] = 0; ++ ctime_r(¤t_time, ctime_buf); ++ ++ /* Remote Peer ++ */ ++ memset(ipVx, 0, sizeof(ipVx)); ++ if (addr && (addr->sa_family == AF_INET)) { ++ strncpy(ipVx, ++ inet_ntoa(((struct sockaddr_in *) addr)->sin_addr), ++ sizeof(ipVx) - 1); ++ port = ntohs(((struct sockaddr_in *) addr)->sin_port); ++ } else if (addr && (addr->sa_family == AF_INET6)) { ++ strncpy(ipVx, ++ inet_ntop(AF_INET6, ++ &(((struct sockaddr_in6 *) addr)->sin6_addr), ++ ipVx, sizeof(ipVx)), ++ sizeof(ipVx) - 1); ++ port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); ++ } ++ ++ memset(port_str, 0, sizeof(port_str)); ++ if (port) ++ snprintf(port_str, sizeof(port_str) -1, ":%d", port); ++ ++ /* Write Summary ++ */ ++ fprintf(fp, "[%ld] %s [%s]%s %s", (long) getpid(), ++ (send ? "Sending to" : "Received from"), ++ ipVx, port_str, ctime_buf); ++ ++ /* Write Packet ++ */ ++ for(ct = 0; ct < len ; ct += 16) { ++ fprintf( fp, "[%ld]", (long) getpid()); ++ for (idx = 0; ((ct + idx) < len) && (idx < 16); idx++) { ++ if (idx && ((idx % 8) == 0)) ++ fprintf( fp, " "); ++ fprintf( fp, " %02x", (buf[ct + idx])); ++ } ++ ++ for ( ; idx < 16 ; idx++) { ++ if (idx && ((idx % 8) == 0)) ++ fprintf( fp, " "); ++ fprintf( fp, " "); ++ } ++ ++ for (idx = 0; ((ct + idx) < len) && (idx < 16); idx++) { ++ if ((idx % 8) == 0) ++ fprintf( fp, " "); ++ if (isprint(buf[ct + idx]) && !isspace(buf[ct + idx])) ++ fprintf( fp, "%c", buf[ct + idx]); ++ else ++ fprintf( fp, "."); ++ } ++ fprintf( fp, "\n"); ++ } ++ ++ ++ fprintf( fp, "\n"); ++ ++#if 0 /* Should be released on close */ ++ flock(fd, LOCK_UN|LOCK_NB); ++#endif ++ ++ fclose(fp); ++ ++} ++ ++ ++ ++#if defined(FOR_TESTING) ++ ++radius_conf_t conf; ++ ++int main(int ac, char * av[]) { ++ ++ struct sockaddr_in sa; ++ struct sockaddr_in6 sa6; ++ ++ conf.statistics = "global"; ++ conf.trace = "packettrace"; ++ ++ _pam_radius_trace(&conf, 1, NULL, (conf.statistics), strlen(conf.statistics)); ++ _pam_radius_trace(&conf, 0, NULL, (conf.trace), strlen(conf.trace)); ++ ++ memset(&sa, 0, sizeof(sa)); ++ memset(&sa6, 0, sizeof(sa6)); ++ ((struct sockaddr *)&sa)->sa_family = AF_INET; ++ ((struct sockaddr *)&sa6)->sa_family = AF_INET6; ++ inet_pton(AF_INET, "10.25.1.2", &(sa.sin_addr)); ++ inet_pton(AF_INET6, "::1", &(sa6.sin6_addr)); ++ sa.sin_port = htons(1812); ++ sa6.sin6_port = htons(1812); ++ ++ _pam_radius_trace(&conf, 1, (struct sockaddr *) &sa, "0123456789012345678901234567890123", 34); ++ _pam_radius_trace(&conf, 0, (struct sockaddr *) &sa6, "0123456789012345678901234567890123", 34); ++} ++ ++#endif +Index: pam_radius/pam_radius +=================================================================== +--- /dev/null ++++ pam_radius/pam_radius +@@ -0,0 +1,9 @@ ++/var/log/pam_radius_trace.log ++{ ++ size 1M ++ rotate 8 ++ missingok ++ notifempty ++ compress ++ delaycompress ++} +Index: pam_radius/Makefile +=================================================================== +--- pam_radius.orig/Makefile ++++ pam_radius/Makefile +@@ -63,6 +63,12 @@ src/mschapv2.o: src/mschapv2.c + src/radpeapclient.o: src/radpeapclient.c src/radpeapclient.h + @$(MAKE) -C src $(notdir $@) + ++src/pam_radius_stats.o: src/pam_radius_stats.c src/pam_radius_stats.h src/pam_radius_auth.h ++ @$(MAKE) -C src $(notdir $@) ++ ++src/pam_radius_trace.o: src/pam_radius_trace.c src/pam_radius_stats.h src/pam_radius_auth.h ++ @$(MAKE) -C src $(notdir $@) ++ + # + # This is what should work on Irix: + #pam_radius_auth.so: pam_radius_auth.o md5.o +@@ -81,7 +87,7 @@ src/radpeapclient.o: src/radpeapclient.c + # + # gcc -shared pam_radius_auth.o md5.o -lpam -lc -o pam_radius_auth.so + # +-pam_radius_auth.so: src/pam_radius_auth.o src/md5.o src/mschapv2.o src/radpeapclient.o ++pam_radius_auth.so: src/pam_radius_auth.o src/md5.o src/mschapv2.o src/radpeapclient.o src/pam_radius_stats.o src/pam_radius_trace.o + $(CC) $(LDFLAGS) $^ -lpam -lradius-1.1.8 -leap-1.1.8 -lssl -o pam_radius_auth.so + + ###################################################################### diff --git a/src/radius/pam/patches/series b/src/radius/pam/patches/series new file mode 100644 index 000000000000..4de1e7c8f2f5 --- /dev/null +++ b/src/radius/pam/patches/series @@ -0,0 +1,3 @@ +0001-chap-support.patch +0002-peap-mschapv2-support.patch +0003-nas-ip-address-config.patch diff --git a/src/sonic-host-services-data/debian/rules b/src/sonic-host-services-data/debian/rules index 02be0c9093d7..22c7450193e8 100755 --- a/src/sonic-host-services-data/debian/rules +++ b/src/sonic-host-services-data/debian/rules @@ -15,6 +15,7 @@ build: override_dh_installsystemd: dh_installsystemd --no-start --name=caclmgrd dh_installsystemd --no-start --name=hostcfgd + dh_installsystemd --no-start --name=aaastatsd dh_installsystemd --no-start --name=procdockerstatsd dh_installsystemd --no-start --name=determine-reboot-cause dh_installsystemd --no-start --name=process-reboot-cause diff --git a/src/sonic-host-services-data/debian/sonic-host-services-data.aaastatsd.service b/src/sonic-host-services-data/debian/sonic-host-services-data.aaastatsd.service new file mode 100644 index 000000000000..b03c6a9551c0 --- /dev/null +++ b/src/sonic-host-services-data/debian/sonic-host-services-data.aaastatsd.service @@ -0,0 +1,17 @@ +[Unit] +Description=AAA Statistics Collection daemon +Requires=hostcfgd.service +After=hostcfgd.service updategraph.service +BindsTo=sonic.target +After=sonic.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/aaastatsd +Restart=on-failure +RestartSec=10 +TimeoutStopSec=3 + +[Install] +WantedBy=sonic.target + diff --git a/src/sonic-host-services-data/templates/common-auth-sonic.j2 b/src/sonic-host-services-data/templates/common-auth-sonic.j2 index a9e95fda19fd..7435d47644b7 100644 --- a/src/sonic-host-services-data/templates/common-auth-sonic.j2 +++ b/src/sonic-host-services-data/templates/common-auth-sonic.j2 @@ -28,6 +28,42 @@ auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not {% endfor %} auth [success=1 default=ignore] pam_unix.so nullok try_first_pass +{% elif auth['login'] == 'local,radius' %} +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass +# For the RADIUS servers, on success jump to the cacheing the MPL(Privilege) +{% for server in servers %} +auth [success={{ (servers | count) - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}:{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }}{% if server.nas_ip is defined %} nas_ip_address={{ server.nas_ip }}{% endif %}{% if server.nas_id is defined %} client_id={{ server.nas_id }}{% endif %}{% if debug %} debug{% endif %}{% if trace %} trace{% endif %}{% if server.statistics %} statistics={{ server.ip }}{% endif %} try_first_pass +{% endfor %} +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius + +{% elif auth['login'] == 'radius,local' %} +# root user can only be authenticated locally. Jump to local. +auth [success={{ (servers | count) }} default=ignore] pam_succeed_if.so user = root +# For the RADIUS servers, on success jump to the cache the MPL(Privilege) +{% for server in servers %} +auth [success={{ (servers | count) + 1 - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}:{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }}{% if server.nas_ip is defined %} nas_ip_address={{ server.nas_ip }}{% endif %}{% if server.nas_id is defined %} client_id={{ server.nas_id }}{% endif %}{% if debug %} debug{% endif %}{% if trace %} trace{% endif %}{% if server.statistics %} statistics={{ server.ip }}{% endif %} try_first_pass +{% endfor %} +# Local +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius + +{% elif auth['login'] == 'radius' %} +# root user can only be authenticated locally. Jump to local. +auth [success={{ (servers | count) + 2 }} default=ignore] pam_succeed_if.so user = root +# For the RADIUS servers, on success jump to the cache the MPL(Privilege) +{% for server in servers %} +auth [success={{ (servers | count) - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}:{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }}{% if server.nas_ip is defined %} nas_ip_address={{ server.nas_ip }}{% endif %}{% if server.nas_id is defined %} client_id={{ server.nas_id }}{% endif %}{% if debug %} debug{% endif %}{% if trace %} trace{% endif %}{% if server.statistics %} statistics={{ server.ip }}{% endif %} try_first_pass +{% endfor %} +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=2 default=ignore] pam_exec.so /usr/sbin/cache_radius +# Local +auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass + {% else %} auth [success=1 default=ignore] pam_unix.so nullok try_first_pass diff --git a/src/sonic-host-services-data/templates/pam_radius_auth.conf.j2 b/src/sonic-host-services-data/templates/pam_radius_auth.conf.j2 new file mode 100644 index 000000000000..7d3c73e1ca9f --- /dev/null +++ b/src/sonic-host-services-data/templates/pam_radius_auth.conf.j2 @@ -0,0 +1,3 @@ +# server[:port] shared_secret timeout(s) source_ip vrf +[{{ server.ip }}]:{{ server.auth_port }} {{ server.passkey }} {{ server.timeout }} {% if server.src_ip %} {{ server.src_ip }} {% endif %} {% if server.vrf %} {% if not server.src_ip %} - {% endif %} {{ server.vrf }}{% endif %} + diff --git a/src/sonic-host-services-data/templates/radius_nss.conf.j2 b/src/sonic-host-services-data/templates/radius_nss.conf.j2 new file mode 100644 index 000000000000..a0da68d39961 --- /dev/null +++ b/src/sonic-host-services-data/templates/radius_nss.conf.j2 @@ -0,0 +1,58 @@ +#THIS IS AN AUTO-GENERATED FILE +# Generated from: /usr/share/sonic/templates/radius_nss.conf.j2 +# RADIUS NSS Configuration File +# +# Debug: on|off|trace +# Default: off +# +# debug=on +{% if debug %} +debug=on +{% endif %} + +# +# User Privilege: +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell + +# Eg: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=7;pw_info=netops;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=operator;gid=100;group=docker;shell=/usr/bin/sonic-launch-shell +# + +# many_to_one: +# y: Map RADIUS users to one local user per privilege. +# n: Create local user account on first successful authentication. +# Default: n +# + +# Eg: +# many_to_one=y +# + +# unconfirmed_disallow: +# y: Do not allow unconfirmed users (users created before authentication) +# n: Allow unconfirmed users. +# Default: n + +# Eg: +# unconfirmed_disallow=y +# + +# unconfirmed_ageout: +# : Wait time before purging unconfirmed users +# Default: 600 +# + +# Eg: +# unconfirmed_ageout=900 +# + +# unconfirmed_regexp: +# : The RE to match the command line of processes for which the +# creation of unconfirmed users are to be allowed. +# Default: (.*: \[priv\])|(.*: \[accepted\]) +# where: is the unconfirmed user. +# diff --git a/src/sonic-host-services/.gitignore b/src/sonic-host-services/.gitignore index 25bc70304637..e807a5b8f7ed 100644 --- a/src/sonic-host-services/.gitignore +++ b/src/sonic-host-services/.gitignore @@ -2,6 +2,7 @@ *.pyc scripts/caclmgrdc scripts/hostcfgdc +scripts/aaastatsdc scripts/procdockerstatsdc # Generated by packaging @@ -15,3 +16,7 @@ dist/ .pytest_cache/ coverage.xml htmlcov/ +test-results.xml + +# Unit test scratchpad +tests/hostcfgd/output/* diff --git a/src/sonic-host-services/pytest.ini b/src/sonic-host-services/pytest.ini index 5a1e1dc94381..ae0c05fcb5f1 100644 --- a/src/sonic-host-services/pytest.ini +++ b/src/sonic-host-services/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --ignore=tests/hostcfgd/test_vectors.py +addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --ignore=tests/hostcfgd/test_vectors.py --ignore=tests/hostcfgd/test_radius_vectors.py diff --git a/src/sonic-host-services/scripts/aaastatsd b/src/sonic-host-services/scripts/aaastatsd new file mode 100755 index 000000000000..57bf7611531b --- /dev/null +++ b/src/sonic-host-services/scripts/aaastatsd @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# + +import os +import syslog +import threading +from swsssdk import ConfigDBConnector +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler + +# FILE +RADIUS_PAM_AUTH_CONF_DIR = "/etc/pam_radius_auth.d/" +RADIUS_PAM_AUTH_CONF_STATS_DIR = "/etc/pam_radius_auth.d/statistics/" + +class RadiusCountersDbMon (threading.Thread): + def __init__(self, ID, name, radiusStatsInstance): + threading.Thread.__init__(self) + self.ID = ID + self.name = name + self.radiusStatsInstance = radiusStatsInstance + + def handle_CountersDbRadiusClear(self, key, data): + # print("RadiusCountersDbMon.handle_CountersDbRadiusClear()") + if key == 'clear': + self.radiusStatsInstance.handle_clear() + + def run(self): + # print("RadiusCountersDbMon.run()") + self.radiusStatsInstance.counters_db.subscribe('RADIUS', lambda table, key, data: self.handle_CountersDbRadiusClear(key, data)) + self.radiusStatsInstance.counters_db.listen() + # print("RadiusCountersDbMon.run(): After listen()") + + +class RadiusStatsFileHandler(FileSystemEventHandler): + def __init__(self, radiusStatsInstance): + self.radiusStatsInstance = radiusStatsInstance + + def on_any_event(self, event): + # print("RadiusStatsFileHandler.on_any_event()") + if event.is_directory: + return None + + self.radiusStatsInstance.handle_update(os.path.basename(event.src_path)) + +class RadiusStatsFileMon (): + def __init__(self, radiusStatsInstance): + self.event_handler = RadiusStatsFileHandler(radiusStatsInstance) + self.observer = Observer() + self.observer.schedule(self.event_handler, RADIUS_PAM_AUTH_CONF_STATS_DIR, recursive=False) + self.observer.start() + # print("RadiusStatsFileMon.__init__(): After observer.start()") + + def stop(self): + # print("RadiusStatsFileMon.stop()") + self.observer.stop() + # print("RadiusStatsFileMon.stop(): After observer.stop()") + self.observer.join() + # print("RadiusStatsFileMon.stop(): After observer.join()") + + +class RadiusStatistics: + def __init__(self, cfg_db, rad_global_conf, radius_conf): + + self.radius_counter_names = [ + "counter_0", + "access_requests", + "access_accepts", + "access_rejects", + "accounting_requests", + "accounting_responses", + "counter_6", + "counter_7", + "counter_8", + "counter_9", + "counter_10", + "access_challenges", + "counter_12", + "counter_13", + "counter_14", + "counter_15", + "counter_16", + "retried_access_requests", + "counter_18", + "counter_19", + "retried_accounting_requests", + "counter_21", + "counter_22", + "counter_23", + "counter_24", + "counter_25", + "counter_26", + "retried_access_challenges", + "counter_28", + "counter_29", + "counter_30", + "counter_31", + "timeouts", + "bad_authenticators", + "invalid_packets", + "counter_35", + ] + + self.radius_global = { + 'statistics': 'False' + } + + self.radius_servers = {} + + self.config_db = cfg_db + + for row in rad_global_conf: + self.radius_global_update(row, rad_global_conf[row]) + + for row in radius_conf: + self.radius_server_update(row, radius_conf[row]) + + + self.counters_db = ConfigDBConnector() + self.counters_db.db_connect('COUNTERS_DB', wait_for_init=False, + retry_on=True) + syslog.syslog(syslog.LOG_INFO, 'CountersDB connect success') + self.dbmon_thread = RadiusCountersDbMon("RadiusCountersDbMon", + "RadiusCountersDbMon", self) + self.dbmon_thread.daemon = True + self.dbmon_thread.start() + + self.filemon = RadiusStatsFileMon(self) + syslog.syslog(syslog.LOG_INFO, 'RADIUS Stats File Monitor started') + + def radius_global_update(self, key, data): + if key == 'global': + self.radius_global.update(data) + + for addr in self.radius_servers: + self.create_file(addr) + + def radius_server_update(self, key, data): + if data == {}: + if key in self.radius_servers: + del self.radius_servers[key] + else: + self.radius_servers[key] = data + + self.create_file(key) + + def create_file(self, addr): + # print( "RadiusStatistics.create_file({})".format(addr)) + stats_file = RADIUS_PAM_AUTH_CONF_STATS_DIR + addr + if self.radius_global['statistics'] == 'False': + if os.path.exists(stats_file): + os.unlink(stats_file) + else: + open(stats_file, 'a').close() + os.chmod(stats_file, 0o666) + self.handle_update(addr) + + def handle_clear(self): + # print( "RadiusStatistics.handle_clear()") + for filename in os.listdir(RADIUS_PAM_AUTH_CONF_STATS_DIR): + stats_file = RADIUS_PAM_AUTH_CONF_STATS_DIR + filename + open(stats_file, 'w').close() + + def handle_update(self, srv): + # print( "RadiusStatistics.handle_update({})".format(srv)) + if self.radius_global['statistics'] == 'False': + return + + stats_file = RADIUS_PAM_AUTH_CONF_STATS_DIR + srv + entry = None + if os.path.exists(stats_file): + with open(stats_file, 'r') as f: + lines = f.readlines() + if len(lines) > 0: + radius_counters = lines[0].split(' ') + entry = dict(zip(self.radius_counter_names, radius_counters)) + + counters_db = ConfigDBConnector() + counters_db.db_connect('COUNTERS_DB', wait_for_init=False, + retry_on=False) + + counters_db.set_entry('RADIUS_SERVER_STATS', srv, entry) + + counters_db.close(counters_db.COUNTERS_DB) + +class AAAStatsDaemon: + def __init__(self): + self.config_db = ConfigDBConnector() + self.config_db.connect(wait_for_init=True, retry_on=True) + syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success') + + radius_global = self.config_db.get_table('RADIUS') + radius_server = self.config_db.get_table('RADIUS_SERVER') + + self.radiusstats = RadiusStatistics(self.config_db, radius_global, + radius_server) + + def radius_global_handler(self, key, data): + self.radiusstats.radius_global_update(key, data) + + def radius_server_handler(self, key, data): + self.radiusstats.radius_server_update(key, data) + + def start(self): + self.config_db.subscribe('RADIUS_SERVER', + lambda table, key, data: self.radius_server_handler(key, data)) + self.config_db.subscribe('RADIUS', + lambda table, key, data: self.radius_global_handler(key, data)) + self.config_db.listen() + # print( "After config_db.listen()") + syslog.syslog(syslog.LOG_INFO, 'Stopping FileMon') + self.radiusstats.filemon.stop() + # print( "Exiting") + syslog.syslog(syslog.LOG_INFO, 'Exiting') + +def main(): + daemon = AAAStatsDaemon() + daemon.start() + + +if __name__ == "__main__": + main() + diff --git a/src/sonic-host-services/scripts/hostcfgd b/src/sonic-host-services/scripts/hostcfgd index 3b207b7fd54f..7f4482121d30 100755 --- a/src/sonic-host-services/scripts/hostcfgd +++ b/src/sonic-host-services/scripts/hostcfgd @@ -16,20 +16,25 @@ PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic" PAM_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/common-auth-sonic.j2" NSS_TACPLUS_CONF = "/etc/tacplus_nss.conf" NSS_TACPLUS_CONF_TEMPLATE = "/usr/share/sonic/templates/tacplus_nss.conf.j2" +NSS_RADIUS_CONF = "/etc/radius_nss.conf" +NSS_RADIUS_CONF_TEMPLATE = "/usr/share/sonic/templates/radius_nss.conf.j2" +PAM_RADIUS_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/pam_radius_auth.conf.j2" NSS_CONF = "/etc/nsswitch.conf" +ETC_PAMD_SSHD = "/etc/pam.d/sshd" +ETC_PAMD_LOGIN = "/etc/pam.d/login" # TACACS+ TACPLUS_SERVER_PASSKEY_DEFAULT = "" TACPLUS_SERVER_TIMEOUT_DEFAULT = "5" TACPLUS_SERVER_AUTH_TYPE_DEFAULT = "pap" -def run_cmd(cmd, log_err = True): - try: - subprocess.check_call(cmd, shell = True) - except Exception as err: - if log_err: - syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}" - .format(err.cmd, err.returncode, err.output)) +# RADIUS +RADIUS_SERVER_AUTH_PORT_DEFAULT = "1812" +RADIUS_SERVER_PASSKEY_DEFAULT = "" +RADIUS_SERVER_RETRANSMIT_DEFAULT = "3" +RADIUS_SERVER_TIMEOUT_DEFAULT = "5" +RADIUS_SERVER_AUTH_TYPE_DEFAULT = "pap" +RADIUS_PAM_AUTH_CONF_DIR = "/etc/pam_radius_auth.d/" def is_true(val): if val == 'True' or val == 'true': @@ -37,6 +42,9 @@ def is_true(val): else: return False +def is_vlan_sub_interface(ifname): + ifname_split = ifname.split(".") + return (len(ifname_split) == 2) def sub(l, start, end): return l[start:end] @@ -56,6 +64,8 @@ def run_cmd(cmd, log_err = True): if log_err: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}" .format(err.cmd, err.returncode, err.output)) + return err.returncode + return 0 class Iptables(object): def __init__(self): @@ -144,19 +154,40 @@ class AaaCfg(object): 'timeout': TACPLUS_SERVER_TIMEOUT_DEFAULT, 'passkey': TACPLUS_SERVER_PASSKEY_DEFAULT } - self.auth = {} self.tacplus_global = {} self.tacplus_servers = {} + + self.radius_global_default = { + 'priority': 0, + 'auth_port': RADIUS_SERVER_AUTH_PORT_DEFAULT, + 'auth_type': RADIUS_SERVER_AUTH_TYPE_DEFAULT, + 'retransmit': RADIUS_SERVER_RETRANSMIT_DEFAULT, + 'timeout': RADIUS_SERVER_TIMEOUT_DEFAULT, + 'passkey': RADIUS_SERVER_PASSKEY_DEFAULT + } + self.radius_global = {} + self.radius_servers = {} + + self.auth = {} self.debug = False + self.trace = False + + self.hostname = "" # Load conf from ConfigDb - def load(self, aaa_conf, tac_global_conf, tacplus_conf): + def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf): for row in aaa_conf: self.aaa_update(row, aaa_conf[row], modify_conf=False) for row in tac_global_conf: self.tacacs_global_update(row, tac_global_conf[row], modify_conf=False) for row in tacplus_conf: self.tacacs_server_update(row, tacplus_conf[row], modify_conf=False) + + for row in rad_global_conf: + self.radius_global_update(row, rad_global_conf[row], modify_conf=False) + for row in radius_conf: + self.radius_server_update(row, radius_conf[row], modify_conf=False) + self.modify_conf_file() def aaa_update(self, key, data, modify_conf=True): @@ -169,6 +200,29 @@ class AaaCfg(object): if modify_conf: self.modify_conf_file() + def pick_src_intf_ipaddrs(self, keys, src_intf): + new_ipv4_addr = "" + new_ipv6_addr = "" + + for it in keys: + if src_intf != it[0] or (isinstance(it, tuple) == False): + continue + if new_ipv4_addr != "" and new_ipv6_addr != "": + break + ip_str = it[1].split("/")[0] + ip_addr = ipaddress.IPAddress(ip_str) + # Pick the first IP address from the table that matches the source interface + if isinstance(ip_addr, ipaddress.IPv6Address): + if new_ipv6_addr != "": + continue + new_ipv6_addr = ip_str + else: + if new_ipv4_addr != "": + continue + new_ipv4_addr = ip_str + + return(new_ipv4_addr, new_ipv6_addr) + def tacacs_global_update(self, key, data, modify_conf=True): if key == 'global': self.tacplus_global = data @@ -185,6 +239,106 @@ class AaaCfg(object): if modify_conf: self.modify_conf_file() + def handle_radius_source_intf_ip_chg(self, key): + modify_conf=False + if 'src_intf' in self.radius_global: + if key[0] == self.radius_global['src_intf']: + modify_conf=True + for addr in self.radius_servers: + if ('src_intf' in self.radius_servers[addr]) and \ + (key[0] == self.radius_servers[addr]['src_intf']): + modify_conf=True + break + + if not modify_conf: + return + + syslog.syslog(syslog.LOG_INFO, 'RADIUS IP change - key:{}, current server info {}'.format(key, self.radius_servers)) + self.modify_conf_file() + + def handle_radius_nas_ip_chg(self, key): + modify_conf=False + # Mgmt IP configuration affects only the default nas_ip + if 'nas_ip' not in self.radius_global: + for addr in self.radius_servers: + if 'nas_ip' not in self.radius_servers[addr]: + modify_conf=True + break + + if not modify_conf: + return + + syslog.syslog(syslog.LOG_INFO, 'RADIUS (NAS) IP change - key:{}, current global info {}'.format(key, self.radius_global)) + self.modify_conf_file() + + def radius_global_update(self, key, data, modify_conf=True): + if key == 'global': + self.radius_global = data + if 'statistics' in data: + self.radius_global['statistics'] = is_true(data['statistics']) + if modify_conf: + self.modify_conf_file() + + def radius_server_update(self, key, data, modify_conf=True): + if data == {}: + if key in self.radius_servers: + del self.radius_servers[key] + else: + self.radius_servers[key] = data + + if modify_conf: + self.modify_conf_file() + + def hostname_update(self, hostname, modify_conf=True): + if self.hostname == hostname: + return + + self.hostname = hostname + + # Currently only used for RADIUS + if len(self.radius_servers) == 0: + return + + if modify_conf: + self.modify_conf_file() + + def get_hostname(self): + return self.hostname + + def get_interface_ip(self, source, addr=None): + keys = None + try: + if source.startswith("Eth"): + if is_vlan_sub_interface(source): + keys = self.config_db.get_keys('VLAN_SUB_INTERFACE') + else: + keys = self.config_db.get_keys('INTERFACE') + elif source.startswith("Po"): + if is_vlan_sub_interface(source): + keys = self.config_db.get_keys('VLAN_SUB_INTERFACE') + else: + keys = self.config_db.get_keys('PORTCHANNEL_INTERFACE') + elif source.startswith("Vlan"): + keys = self.config_db.get_keys('VLAN_INTERFACE') + elif source.startswith("Loopback"): + keys = self.config_db.get_keys('LOOPBACK_INTERFACE') + elif source == "eth0": + keys = self.config_db.get_keys('MGMT_INTERFACE') + except Exception as e: + pass + + interface_ip = "" + if keys != None: + ipv4_addr, ipv6_addr = self.pick_src_intf_ipaddrs(keys, source) + # Based on the type of addr, return v4 or v6 + if addr and isinstance(addr, ipaddress.IPv6Address): + interface_ip = ipv6_addr + else: + # This could be tuned, but that involves a DNS query, so + # offline configuration might trip (or cause delays). + interface_ip = ipv4_addr + return interface_ip + def modify_single_file(self, filename, operations=None): if operations: cmd = "sed -e {0} {1} > {1}.new; mv -f {1} {1}.old; mv -f {1}.new {1}".format(' -e '.join(operations), filename) @@ -209,29 +363,82 @@ class AaaCfg(object): servers_conf.append(server) servers_conf = sorted(servers_conf, key=lambda t: int(t['priority']), reverse=True) + radius_global = self.radius_global_default.copy() + radius_global.update(self.radius_global) + + # RADIUS: Set the default nas_ip, and nas_id + if 'nas_ip' not in radius_global: + nas_ip = self.get_interface_ip("eth0") + if len(nas_ip) > 0: + radius_global['nas_ip'] = nas_ip + if 'nas_id' not in radius_global: + nas_id = self.get_hostname() + if len(nas_id) > 0: + radius_global['nas_id'] = nas_id + + radsrvs_conf = [] + if self.radius_servers: + for addr in self.radius_servers: + server = radius_global.copy() + server['ip'] = addr + server.update(self.radius_servers[addr]) + + if 'src_intf' in server: + # RADIUS: Log a message if src_ip is already defined. + if 'src_ip' in server: + syslog.syslog(syslog.LOG_INFO, \ + "RADIUS_SERVER|{}: src_intf found. Ignoring src_ip".format(addr)) + # RADIUS: If server.src_intf, then get the corresponding + # src_ip based on the server.ip, and set it. + src_ip = self.get_interface_ip(server['src_intf'], addr) + if len(src_ip) > 0: + server['src_ip'] = src_ip + elif 'src_ip' in server: + syslog.syslog(syslog.LOG_INFO, \ + "RADIUS_SERVER|{}: src_intf has no usable IP addr.".format(addr)) + del server['src_ip'] + + radsrvs_conf.append(server) + radsrvs_conf = sorted(radsrvs_conf, key=lambda t: int(t['priority']), reverse=True) + template_file = os.path.abspath(PAM_AUTH_CONF_TEMPLATE) env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) env.filters['sub'] = sub template = env.get_template(template_file) - pam_conf = template.render(auth=auth, src_ip=src_ip, servers=servers_conf) - with open(PAM_AUTH_CONF, 'w') as f: + if 'radius' in auth['login']: + pam_conf = template.render(debug=self.debug, trace=self.trace, auth=auth, servers=radsrvs_conf) + else: + pam_conf = template.render(auth=auth, src_ip=src_ip, servers=servers_conf) + + # Use rename(), which is atomic (on the same fs) to avoid empty file + with open(PAM_AUTH_CONF + ".tmp", 'w') as f: f.write(pam_conf) + os.chmod(PAM_AUTH_CONF + ".tmp", 0o644) + os.rename(PAM_AUTH_CONF + ".tmp", PAM_AUTH_CONF) - # Modify common-auth include file in /etc/pam.d/login and sshd + # Modify common-auth include file in /etc/pam.d/login, sshd. + # /etc/pam.d/sudo is not handled, because it would change the existing + # behavior. It can be modified once a config knob is added for sudo. if os.path.isfile(PAM_AUTH_CONF): - self.modify_single_file('/etc/pam.d/sshd', [ "'/^@include/s/common-auth$/common-auth-sonic/'" ]) - self.modify_single_file('/etc/pam.d/login', [ "'/^@include/s/common-auth$/common-auth-sonic/'" ]) + self.modify_single_file(ETC_PAMD_SSHD, [ "'/^@include/s/common-auth$/common-auth-sonic/'" ]) + self.modify_single_file(ETC_PAMD_LOGIN, [ "'/^@include/s/common-auth$/common-auth-sonic/'" ]) else: - self.modify_single_file('/etc/pam.d/sshd', [ "'/^@include/s/common-auth-sonic$/common-auth/'" ]) - self.modify_single_file('/etc/pam.d/login', [ "'/^@include/s/common-auth-sonic$/common-auth/'" ]) + self.modify_single_file(ETC_PAMD_SSHD, [ "'/^@include/s/common-auth-sonic$/common-auth/'" ]) + self.modify_single_file(ETC_PAMD_LOGIN, [ "'/^@include/s/common-auth-sonic$/common-auth/'" ]) - # Add tacplus in nsswitch.conf if TACACS+ enable + # Add tacplus/radius in nsswitch.conf if TACACS+/RADIUS enable if 'tacacs+' in auth['login']: if os.path.isfile(NSS_CONF): + self.modify_single_file(NSS_CONF, [ "'/^passwd/s/ radius//'" ]) self.modify_single_file(NSS_CONF, [ "'/tacplus/b'", "'/^passwd/s/compat/tacplus &/'", "'/^passwd/s/files/tacplus &/'" ]) + elif 'radius' in auth['login']: + if os.path.isfile(NSS_CONF): + self.modify_single_file(NSS_CONF, [ "'/^passwd/s/tacplus //'" ]) + self.modify_single_file(NSS_CONF, [ "'/radius/b'", "'/^passwd/s/compat/& radius/'", "'/^passwd/s/files/& radius/'" ]) else: if os.path.isfile(NSS_CONF): self.modify_single_file(NSS_CONF, [ "'/^passwd/s/tacplus //g'" ]) + self.modify_single_file(NSS_CONF, [ "'/^passwd/s/ radius//'" ]) # Set tacacs+ server in nss-tacplus conf template_file = os.path.abspath(NSS_TACPLUS_CONF_TEMPLATE) @@ -240,6 +447,41 @@ class AaaCfg(object): with open(NSS_TACPLUS_CONF, 'w') as f: f.write(nss_tacplus_conf) + # Set debug in nss-radius conf + template_file = os.path.abspath(NSS_RADIUS_CONF_TEMPLATE) + template = env.get_template(template_file) + nss_radius_conf = template.render(debug=self.debug, trace=self.trace, servers=radsrvs_conf) + with open(NSS_RADIUS_CONF, 'w') as f: + f.write(nss_radius_conf) + + # Create the per server pam_radius_auth.conf + if radsrvs_conf: + for srv in radsrvs_conf: + # Configuration File + pam_radius_auth_file = RADIUS_PAM_AUTH_CONF_DIR + srv['ip'] + ":" + srv['auth_port'] + ".conf" + template_file = os.path.abspath(PAM_RADIUS_AUTH_CONF_TEMPLATE) + template = env.get_template(template_file) + pam_radius_auth_conf = template.render(server=srv) + + open(pam_radius_auth_file, 'a').close() + os.chmod(pam_radius_auth_file, 0o600) + with open(pam_radius_auth_file, 'w+') as f: + f.write(pam_radius_auth_conf) + + # Start the statistics service. Only RADIUS implemented + if ('radius' in auth['login']) and ('statistics' in radius_global) and\ + radius_global['statistics']: + cmd = 'service aaastatsd start' + else: + cmd = 'service aaastatsd stop' + syslog.syslog(syslog.LOG_INFO, "cmd - {}".format(cmd)) + try: + subprocess.check_call(cmd, shell=True) + except subprocess.CalledProcessError as err: + syslog.syslog(syslog.LOG_ERR, + "{} - failed: return code - {}, output:\n{}" + .format(err.cmd, err.returncode, err.output)) + class KdumpCfg(object): def __init__(self, CfgDb): self.config_db = CfgDb @@ -398,6 +640,7 @@ class HostConfigDaemon: self.device_config = {} self.device_config['DEVICE_METADATA'] = self.config_db.get_table('DEVICE_METADATA') + self.hostname_cache="" self.aaacfg = AaaCfg() self.iptables = Iptables() self.ntpcfg = NtpCfg(self.config_db) @@ -415,7 +658,9 @@ class HostConfigDaemon: aaa = self.config_db.get_table('AAA') tacacs_global = self.config_db.get_table('TACPLUS') tacacs_server = self.config_db.get_table('TACPLUS_SERVER') - self.aaacfg.load(aaa, tacacs_global, tacacs_server) + radius_global = self.config_db.get_table('RADIUS') + radius_server = self.config_db.get_table('RADIUS_SERVER') + self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server) lpbk_table = self.config_db.get_table('LOOPBACK_INTERFACE') self.iptables.load(lpbk_table) @@ -425,6 +670,16 @@ class HostConfigDaemon: ntp_global = self.config_db.get_table('NTP') self.ntpcfg.load(ntp_global, ntp_server) + try: + dev_meta = self.config_db.get_table('DEVICE_METADATA') + if 'localhost' in dev_meta: + if 'hostname' in dev_meta['localhost']: + self.hostname_cache = dev_meta['localhost']['hostname'] + except Exception as e: + pass + # Update AAA with the hostname + self.aaacfg.hostname_update(self.hostname_cache) + def get_target_state(self, feature_name, state): template = jinja2.Template(state) target_state = template.render(self.device_config) @@ -561,6 +816,24 @@ class HostConfigDaemon: log_data['passkey'] = obfuscate(log_data['passkey']) syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data)) + def radius_server_handler(self, key, data): + self.aaacfg.radius_server_update(key, data) + log_data = copy.deepcopy(data) + if 'passkey' in log_data: + log_data['passkey'] = obfuscate(log_data['passkey']) + syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data)) + + def radius_global_handler(self, key, data): + self.aaacfg.radius_global_update(key, data) + log_data = copy.deepcopy(data) + if 'passkey' in log_data: + log_data['passkey'] = obfuscate(log_data['passkey']) + syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data)) + + def mgmt_intf_handler(self, key, data): + self.aaacfg.handle_radius_source_intf_ip_chg(key) + self.aaacfg.handle_radius_nas_ip_chg(key) + def lpbk_handler(self, key, data): key = ConfigDBConnector.deserialize_key(key) # Check if delete operation by fetch existing keys @@ -572,6 +845,23 @@ class HostConfigDaemon: self.iptables.iptables_handler(key, data, add) self.ntpcfg.handle_ntp_source_intf_chg(key) + self.aaacfg.handle_radius_source_intf_ip_chg(key) + + def vlan_intf_handler(self, key, data): + key = ConfigDBConnector.deserialize_key(key) + self.aaacfg.handle_radius_source_intf_ip_chg(key) + + def vlan_sub_intf_handler(self, key, data): + key = ConfigDBConnector.deserialize_key(key) + self.aaacfg.handle_radius_source_intf_ip_chg(key) + + def portchannel_intf_handler(self, key, data): + key = ConfigDBConnector.deserialize_key(key) + self.aaacfg.handle_radius_source_intf_ip_chg(key) + + def phy_intf_handler(self, key, data): + key = ConfigDBConnector.deserialize_key(key) + self.aaacfg.handle_radius_source_intf_ip_chg(key) def feature_state_handler(self, key, data): feature_name = key @@ -617,7 +907,14 @@ class HostConfigDaemon: self.config_db.subscribe('AAA', lambda table, key, data: self.aaa_handler(key, data)) self.config_db.subscribe('TACPLUS_SERVER', lambda table, key, data: self.tacacs_server_handler(key, data)) self.config_db.subscribe('TACPLUS', lambda table, key, data: self.tacacs_global_handler(key, data)) + self.config_db.subscribe('RADIUS_SERVER', lambda table, key, data: self.radius_server_handler(key, data)) + self.config_db.subscribe('RADIUS', lambda table, key, data: self.radius_global_handler(key, data)) + self.config_db.subscribe('MGMT_INTERFACE', lambda table, key, data: self.mgmt_intf_handler(key, data)) self.config_db.subscribe('LOOPBACK_INTERFACE', lambda table, key, data: self.lpbk_handler(key, data)) + self.config_db.subscribe('VLAN_INTERFACE', lambda table, key, data: self.vlan_intf_handler(key, data)) + self.config_db.subscribe('VLAN_SUB_INTERFACE', lambda table, key, data: self.vlan_sub_intf_handler(key, data)) + self.config_db.subscribe('PORTCHANNEL_INTERFACE', lambda table, key, data: self.portchannel_intf_handler(key, data)) + self.config_db.subscribe('INTERFACE', lambda table, key, data: self.phy_intf_handler(key, data)) self.config_db.subscribe('FEATURE', lambda table, key, data: self.feature_state_handler(key, data)) self.config_db.subscribe('NTP_SERVER', lambda table, key, data: self.ntp_server_handler(key, data)) self.config_db.subscribe('NTP', lambda table, key, data: self.ntp_global_handler(key, data)) diff --git a/src/sonic-host-services/setup.py b/src/sonic-host-services/setup.py index 057ae6f18596..5c1d421bfd0c 100644 --- a/src/sonic-host-services/setup.py +++ b/src/sonic-host-services/setup.py @@ -16,6 +16,7 @@ scripts = [ 'scripts/caclmgrd', 'scripts/hostcfgd', + 'scripts/aaastatsd', 'scripts/procdockerstatsd', 'scripts/determine-reboot-cause', 'scripts/process-reboot-cause', diff --git a/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py b/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py new file mode 100644 index 000000000000..e327ab5b0c17 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/hostcfgd_radius_test.py @@ -0,0 +1,102 @@ +import importlib.machinery +import importlib.util +import filecmp +import shutil +import os +import sys +import subprocess +import swsssdk + +from parameterized import parameterized +from unittest import TestCase, mock +from tests.hostcfgd.test_radius_vectors import HOSTCFGD_TEST_RADIUS_VECTOR +from tests.hostcfgd.mock_configdb import MockConfigDb + + +swsssdk.ConfigDBConnector = MockConfigDb +test_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +modules_path = os.path.dirname(test_path) +scripts_path = os.path.join(modules_path, "scripts") +src_path = os.path.dirname(modules_path) +templates_path = os.path.join(src_path, "sonic-host-services-data/templates") +output_path = os.path.join(test_path, "hostcfgd/output") +sample_output_path = os.path.join(test_path, "hostcfgd/sample_output") +sys.path.insert(0, modules_path) + +# Load the file under test +hostcfgd_path = os.path.join(scripts_path, 'hostcfgd') +loader = importlib.machinery.SourceFileLoader('hostcfgd', hostcfgd_path) +spec = importlib.util.spec_from_loader(loader.name, loader) +hostcfgd = importlib.util.module_from_spec(spec) +loader.exec_module(hostcfgd) +sys.modules['hostcfgd'] = hostcfgd + + +class TestHostcfgdRADIUS(TestCase): + """ + Test hostcfd daemon - RADIUS + """ + def run_diff(self, file1, file2): + return subprocess.check_output('diff -uR {} {} || true'.format(file1, file2), shell=True) + + + @parameterized.expand(HOSTCFGD_TEST_RADIUS_VECTOR) + def test_hostcfgd_radius(self, test_name, test_data): + """ + Test RADIUS hostcfd daemon initialization + + Args: + test_name(str): test name + test_data(dict): test data which contains initial Config Db tables, and expected results + + Returns: + None + """ + + t_path = templates_path + op_path = output_path + "/" + test_name + sop_path = sample_output_path + "/" + test_name + + hostcfgd.PAM_AUTH_CONF_TEMPLATE = t_path + "/common-auth-sonic.j2" + hostcfgd.NSS_TACPLUS_CONF_TEMPLATE = t_path + "/tacplus_nss.conf.j2" + hostcfgd.NSS_RADIUS_CONF_TEMPLATE = t_path + "/radius_nss.conf.j2" + hostcfgd.PAM_RADIUS_AUTH_CONF_TEMPLATE = t_path + "/pam_radius_auth.conf.j2" + hostcfgd.PAM_AUTH_CONF = op_path + "/common-auth-sonic" + hostcfgd.NSS_TACPLUS_CONF = op_path + "/tacplus_nss.conf" + hostcfgd.NSS_RADIUS_CONF = op_path + "/radius_nss.conf" + hostcfgd.NSS_CONF = op_path + "/nsswitch.conf" + hostcfgd.ETC_PAMD_SSHD = op_path + "/sshd" + hostcfgd.ETC_PAMD_LOGIN = op_path + "/login" + hostcfgd.RADIUS_PAM_AUTH_CONF_DIR = op_path + "/" + + shutil.rmtree( op_path, ignore_errors=True) + os.mkdir( op_path) + + shutil.copyfile( sop_path + "/sshd.old", op_path + "/sshd") + shutil.copyfile( sop_path + "/login.old", op_path + "/login") + + MockConfigDb.set_config_db(test_data["config_db"]) + host_config_daemon = hostcfgd.HostConfigDaemon() + + aaa = host_config_daemon.config_db.get_table('AAA') + + try: + radius_global = host_config_daemon.config_db.get_table('RADIUS') + except: + radius_global = [] + try: + radius_server = \ + host_config_daemon.config_db.get_table('RADIUS_SERVER') + except: + radius_server = [] + + host_config_daemon.aaacfg.load(aaa,[],[],radius_global,radius_server) + dcmp = filecmp.dircmp(sop_path, op_path) + diff_output = "" + for name in dcmp.diff_files: + diff_output += \ + "Diff: file: {} expected: {} output: {}\n".format(\ + name, dcmp.left, dcmp.right) + diff_output += self.run_diff( dcmp.left + "/" + name,\ + dcmp.right + "/" + name) + self.assertTrue(len(diff_output) == 0, diff_output) diff --git a/src/sonic-host-services/tests/hostcfgd/output/.gitignore b/src/sonic-host-services/tests/hostcfgd/output/.gitignore new file mode 100644 index 000000000000..3f4e276cb2df --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/output/.gitignore @@ -0,0 +1,4 @@ +# Ignore all test generated files +* +# But keep this file +!.gitignore diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/common-auth-sonic b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/common-auth-sonic new file mode 100644 index 000000000000..87af4cc5c6ac --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/common-auth-sonic @@ -0,0 +1,21 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-auth- authentication settings common to all services +# This file is included from other service-specific PAM config files, +# and should contain a list of the authentication modules that define +# the central authentication scheme for use on the system +# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the +# traditional Unix authentication mechanisms. +# +# here are the per-package modules (the "Primary" block) + +auth [success=1 default=ignore] pam_unix.so nullok try_first_pass + +# +# here's the fallback if no module succeeds +auth requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +auth required pam_permit.so +# and here are more per-package modules (the "Additional" block) diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/login b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/login new file mode 100644 index 000000000000..80ba6452813f --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/login @@ -0,0 +1,116 @@ +# +# The PAM configuration file for the Shadow `login' service +# + +# Enforce a minimal delay in case of failure (in microseconds). +# (Replaces the `FAIL_DELAY' setting from login.defs) +# Note that other modules may require another minimal delay. (for example, +# to disable any delay, you should add the nodelay option to pam_unix) +auth optional pam_faildelay.so delay=3000000 + +# Outputs an issue file prior to each login prompt (Replaces the +# ISSUE_FILE option from login.defs). Uncomment for use +# auth required pam_issue.so issue=/etc/issue + +# Disallows root logins except on tty's listed in /etc/securetty +# (Replaces the `CONSOLE' setting from login.defs) +# +# With the default control of this module: +# [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] +# root will not be prompted for a password on insecure lines. +# if an invalid username is entered, a password is prompted (but login +# will eventually be rejected) +# +# You can change it to a "requisite" module if you think root may mis-type +# her login and should not be prompted for a password in that case. But +# this will leave the system as vulnerable to user enumeration attacks. +# +# You can change it to a "required" module if you think it permits to +# guess valid user names of your system (invalid user names are considered +# as possibly being root on insecure lines), but root passwords may be +# communicated over insecure lines. +auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible +# that a module could execute code in the wrong domain. +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Sets the loginuid process attribute +session required pam_loginuid.so + +# SELinux needs to intervene at login time to ensure that the process +# starts in the proper default security context. Only sessions which are +# intended to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) + +# This module parses environment configuration file(s) +# and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# +# parsing /etc/environment needs "readenv=1" +session required pam_env.so readenv=1 +# locale variables are also kept into /etc/default/locale in etch +# reading this file *in addition to /etc/environment* does not hurt +session required pam_env.so readenv=1 envfile=/etc/default/locale + +# Standard Un*x authentication. +@include common-auth-sonic + +# This allows certain extra groups to be granted to a user +# based on things like time of day, tty, service, and user. +# Please edit /etc/security/group.conf to fit your needs +# (Replaces the `CONSOLE_GROUPS' option in login.defs) +auth optional pam_group.so + +# Uncomment and edit /etc/security/time.conf if you need to set +# time restraint on logins. +# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs +# as well as /etc/porttime) +# account requisite pam_time.so + +# Uncomment and edit /etc/security/access.conf if you need to +# set access limits. +# (Replaces /etc/login.access file) +# account required pam_access.so + +# Sets up user limits according to /etc/security/limits.conf +# (Replaces the use of /etc/limits in old login) +session required pam_limits.so + +# Prints the last login info upon successful login +# (Replaces the `LASTLOG_ENAB' option from login.defs) +session optional pam_lastlog.so + +# Prints the message of the day upon successful login. +# (Replaces the `MOTD_FILE' option in login.defs) +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Prints the status of the user's mailbox upon successful login +# (Replaces the `MAIL_CHECK_ENAB' option from login.defs). +# +# This also defines the MAIL environment variable +# However, userdel also needs MAIL_DIR and MAIL_FILE variables +# in /etc/login.defs to make sure that removing a user +# also removes the user's mail spool file. +# See comments in /etc/login.defs +session optional pam_mail.so standard + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x account and session +@include common-account +@include common-session +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/login.old b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/login.old new file mode 100644 index 000000000000..07ff95407ccd --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/login.old @@ -0,0 +1,116 @@ +# +# The PAM configuration file for the Shadow `login' service +# + +# Enforce a minimal delay in case of failure (in microseconds). +# (Replaces the `FAIL_DELAY' setting from login.defs) +# Note that other modules may require another minimal delay. (for example, +# to disable any delay, you should add the nodelay option to pam_unix) +auth optional pam_faildelay.so delay=3000000 + +# Outputs an issue file prior to each login prompt (Replaces the +# ISSUE_FILE option from login.defs). Uncomment for use +# auth required pam_issue.so issue=/etc/issue + +# Disallows root logins except on tty's listed in /etc/securetty +# (Replaces the `CONSOLE' setting from login.defs) +# +# With the default control of this module: +# [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] +# root will not be prompted for a password on insecure lines. +# if an invalid username is entered, a password is prompted (but login +# will eventually be rejected) +# +# You can change it to a "requisite" module if you think root may mis-type +# her login and should not be prompted for a password in that case. But +# this will leave the system as vulnerable to user enumeration attacks. +# +# You can change it to a "required" module if you think it permits to +# guess valid user names of your system (invalid user names are considered +# as possibly being root on insecure lines), but root passwords may be +# communicated over insecure lines. +auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible +# that a module could execute code in the wrong domain. +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Sets the loginuid process attribute +session required pam_loginuid.so + +# SELinux needs to intervene at login time to ensure that the process +# starts in the proper default security context. Only sessions which are +# intended to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) + +# This module parses environment configuration file(s) +# and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# +# parsing /etc/environment needs "readenv=1" +session required pam_env.so readenv=1 +# locale variables are also kept into /etc/default/locale in etch +# reading this file *in addition to /etc/environment* does not hurt +session required pam_env.so readenv=1 envfile=/etc/default/locale + +# Standard Un*x authentication. +@include common-auth + +# This allows certain extra groups to be granted to a user +# based on things like time of day, tty, service, and user. +# Please edit /etc/security/group.conf to fit your needs +# (Replaces the `CONSOLE_GROUPS' option in login.defs) +auth optional pam_group.so + +# Uncomment and edit /etc/security/time.conf if you need to set +# time restraint on logins. +# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs +# as well as /etc/porttime) +# account requisite pam_time.so + +# Uncomment and edit /etc/security/access.conf if you need to +# set access limits. +# (Replaces /etc/login.access file) +# account required pam_access.so + +# Sets up user limits according to /etc/security/limits.conf +# (Replaces the use of /etc/limits in old login) +session required pam_limits.so + +# Prints the last login info upon successful login +# (Replaces the `LASTLOG_ENAB' option from login.defs) +session optional pam_lastlog.so + +# Prints the message of the day upon successful login. +# (Replaces the `MOTD_FILE' option in login.defs) +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Prints the status of the user's mailbox upon successful login +# (Replaces the `MAIL_CHECK_ENAB' option from login.defs). +# +# This also defines the MAIL environment variable +# However, userdel also needs MAIL_DIR and MAIL_FILE variables +# in /etc/login.defs to make sure that removing a user +# also removes the user's mail spool file. +# See comments in /etc/login.defs +session optional pam_mail.so standard + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x account and session +@include common-account +@include common-session +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/radius_nss.conf b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/radius_nss.conf new file mode 100644 index 000000000000..8c31db9fbafe --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/radius_nss.conf @@ -0,0 +1,56 @@ +#THIS IS AN AUTO-GENERATED FILE +# Generated from: /usr/share/sonic/templates/radius_nss.conf.j2 +# RADIUS NSS Configuration File +# +# Debug: on|off|trace +# Default: off +# +# debug=on +debug=on + +# +# User Privilege: +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell + +# Eg: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=7;pw_info=netops;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=operator;gid=100;group=docker;shell=/usr/bin/sonic-launch-shell +# + +# many_to_one: +# y: Map RADIUS users to one local user per privilege. +# n: Create local user account on first successful authentication. +# Default: n +# + +# Eg: +# many_to_one=y +# + +# unconfirmed_disallow: +# y: Do not allow unconfirmed users (users created before authentication) +# n: Allow unconfirmed users. +# Default: n + +# Eg: +# unconfirmed_disallow=y +# + +# unconfirmed_ageout: +# : Wait time before purging unconfirmed users +# Default: 600 +# + +# Eg: +# unconfirmed_ageout=900 +# + +# unconfirmed_regexp: +# : The RE to match the command line of processes for which the +# creation of unconfirmed users are to be allowed. +# Default: (.*: \[priv\])|(.*: \[accepted\]) +# where: is the unconfirmed user. +# \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/sshd b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/sshd new file mode 100644 index 000000000000..c025af353d6f --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/sshd @@ -0,0 +1,55 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +@include common-auth-sonic + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so user_readenv=1 envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/sshd.old b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/sshd.old new file mode 100644 index 000000000000..d70b384bd9a3 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/sshd.old @@ -0,0 +1,55 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +@include common-auth + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so user_readenv=1 envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/tacplus_nss.conf b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/tacplus_nss.conf new file mode 100644 index 000000000000..fe506941f98d --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/LOCAL/tacplus_nss.conf @@ -0,0 +1,23 @@ +# Configuration for libnss-tacplus + +# debug - If you want to open debug log, set it on +# Default: off +# debug=on +debug=on + +# src_ip - set source address of TACACS+ protocol packets +# Default: None (auto source ip address) +# src_ip=2.2.2.2 + +# server - set ip address, tcp port, secret string and timeout for TACACS+ servers +# Default: None (no TACACS+ server) +# server=1.1.1.1:49,secret=test,timeout=3 + +# user_priv - set the map between TACACS+ user privilege and local user's passwd +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/bin/bash +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/bin/bash + +# many_to_one - create one local user for many TACACS+ users which has the same privilege +# Default: many_to_one=n +# many_to_one=y diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/10.10.10.1:1645.conf b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/10.10.10.1:1645.conf new file mode 100644 index 000000000000..ffdda7b401a7 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/10.10.10.1:1645.conf @@ -0,0 +1,2 @@ +# server[:port] shared_secret timeout(s) source_ip vrf +[10.10.10.1]:1645 pass1 1 \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/10.10.10.2:1645.conf b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/10.10.10.2:1645.conf new file mode 100644 index 000000000000..3b680807cbcf --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/10.10.10.2:1645.conf @@ -0,0 +1,2 @@ +# server[:port] shared_secret timeout(s) source_ip vrf +[10.10.10.2]:1645 pass2 2 \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/common-auth-sonic b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/common-auth-sonic new file mode 100644 index 000000000000..12294d0c4d4e --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/common-auth-sonic @@ -0,0 +1,30 @@ +#THIS IS AN AUTO-GENERATED FILE +# +# /etc/pam.d/common-auth- authentication settings common to all services +# This file is included from other service-specific PAM config files, +# and should contain a list of the authentication modules that define +# the central authentication scheme for use on the system +# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the +# traditional Unix authentication mechanisms. +# +# here are the per-package modules (the "Primary" block) + +# root user can only be authenticated locally. Jump to local. +auth [success=2 default=ignore] pam_succeed_if.so user = root +# For the RADIUS servers, on success jump to the cache the MPL(Privilege) +auth [success=3 new_authtok_reqd=done default=ignore auth_err=die] pam_radius_auth.so conf=/etc/pam_radius_auth.d/10.10.10.1:1645.conf privilege_level protocol=pap retry=1 nas_ip_address=10.10.10.10 debug try_first_pass +auth [success=2 new_authtok_reqd=done default=ignore auth_err=die] pam_radius_auth.so conf=/etc/pam_radius_auth.d/10.10.10.2:1645.conf privilege_level protocol=chap retry=2 nas_ip_address=10.10.10.10 debug try_first_pass +# Local +auth [success=done new_authtok_reqd=done default=ignore auth_err=die maxtries=die] pam_unix.so nullok try_first_pass +auth requisite pam_deny.so +# Cache MPL(Privilege) +auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius + +# +# here's the fallback if no module succeeds +auth requisite pam_deny.so +# prime the stack with a positive return value if there isn't one already; +# this avoids us returning an error just because nothing sets a success code +# since the modules above will each just jump around +auth required pam_permit.so +# and here are more per-package modules (the "Additional" block) diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/login b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/login new file mode 100644 index 000000000000..80ba6452813f --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/login @@ -0,0 +1,116 @@ +# +# The PAM configuration file for the Shadow `login' service +# + +# Enforce a minimal delay in case of failure (in microseconds). +# (Replaces the `FAIL_DELAY' setting from login.defs) +# Note that other modules may require another minimal delay. (for example, +# to disable any delay, you should add the nodelay option to pam_unix) +auth optional pam_faildelay.so delay=3000000 + +# Outputs an issue file prior to each login prompt (Replaces the +# ISSUE_FILE option from login.defs). Uncomment for use +# auth required pam_issue.so issue=/etc/issue + +# Disallows root logins except on tty's listed in /etc/securetty +# (Replaces the `CONSOLE' setting from login.defs) +# +# With the default control of this module: +# [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] +# root will not be prompted for a password on insecure lines. +# if an invalid username is entered, a password is prompted (but login +# will eventually be rejected) +# +# You can change it to a "requisite" module if you think root may mis-type +# her login and should not be prompted for a password in that case. But +# this will leave the system as vulnerable to user enumeration attacks. +# +# You can change it to a "required" module if you think it permits to +# guess valid user names of your system (invalid user names are considered +# as possibly being root on insecure lines), but root passwords may be +# communicated over insecure lines. +auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible +# that a module could execute code in the wrong domain. +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Sets the loginuid process attribute +session required pam_loginuid.so + +# SELinux needs to intervene at login time to ensure that the process +# starts in the proper default security context. Only sessions which are +# intended to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) + +# This module parses environment configuration file(s) +# and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# +# parsing /etc/environment needs "readenv=1" +session required pam_env.so readenv=1 +# locale variables are also kept into /etc/default/locale in etch +# reading this file *in addition to /etc/environment* does not hurt +session required pam_env.so readenv=1 envfile=/etc/default/locale + +# Standard Un*x authentication. +@include common-auth-sonic + +# This allows certain extra groups to be granted to a user +# based on things like time of day, tty, service, and user. +# Please edit /etc/security/group.conf to fit your needs +# (Replaces the `CONSOLE_GROUPS' option in login.defs) +auth optional pam_group.so + +# Uncomment and edit /etc/security/time.conf if you need to set +# time restraint on logins. +# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs +# as well as /etc/porttime) +# account requisite pam_time.so + +# Uncomment and edit /etc/security/access.conf if you need to +# set access limits. +# (Replaces /etc/login.access file) +# account required pam_access.so + +# Sets up user limits according to /etc/security/limits.conf +# (Replaces the use of /etc/limits in old login) +session required pam_limits.so + +# Prints the last login info upon successful login +# (Replaces the `LASTLOG_ENAB' option from login.defs) +session optional pam_lastlog.so + +# Prints the message of the day upon successful login. +# (Replaces the `MOTD_FILE' option in login.defs) +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Prints the status of the user's mailbox upon successful login +# (Replaces the `MAIL_CHECK_ENAB' option from login.defs). +# +# This also defines the MAIL environment variable +# However, userdel also needs MAIL_DIR and MAIL_FILE variables +# in /etc/login.defs to make sure that removing a user +# also removes the user's mail spool file. +# See comments in /etc/login.defs +session optional pam_mail.so standard + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x account and session +@include common-account +@include common-session +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/login.old b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/login.old new file mode 100644 index 000000000000..07ff95407ccd --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/login.old @@ -0,0 +1,116 @@ +# +# The PAM configuration file for the Shadow `login' service +# + +# Enforce a minimal delay in case of failure (in microseconds). +# (Replaces the `FAIL_DELAY' setting from login.defs) +# Note that other modules may require another minimal delay. (for example, +# to disable any delay, you should add the nodelay option to pam_unix) +auth optional pam_faildelay.so delay=3000000 + +# Outputs an issue file prior to each login prompt (Replaces the +# ISSUE_FILE option from login.defs). Uncomment for use +# auth required pam_issue.so issue=/etc/issue + +# Disallows root logins except on tty's listed in /etc/securetty +# (Replaces the `CONSOLE' setting from login.defs) +# +# With the default control of this module: +# [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] +# root will not be prompted for a password on insecure lines. +# if an invalid username is entered, a password is prompted (but login +# will eventually be rejected) +# +# You can change it to a "requisite" module if you think root may mis-type +# her login and should not be prompted for a password in that case. But +# this will leave the system as vulnerable to user enumeration attacks. +# +# You can change it to a "required" module if you think it permits to +# guess valid user names of your system (invalid user names are considered +# as possibly being root on insecure lines), but root passwords may be +# communicated over insecure lines. +auth [success=ok new_authtok_reqd=ok ignore=ignore user_unknown=bad default=die] pam_securetty.so + +# Disallows other than root logins when /etc/nologin exists +# (Replaces the `NOLOGINS_FILE' option from login.defs) +auth requisite pam_nologin.so + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible +# that a module could execute code in the wrong domain. +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Sets the loginuid process attribute +session required pam_loginuid.so + +# SELinux needs to intervene at login time to ensure that the process +# starts in the proper default security context. Only sessions which are +# intended to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open +# When the module is present, "required" would be sufficient (When SELinux +# is disabled, this returns success.) + +# This module parses environment configuration file(s) +# and also allows you to use an extended config +# file /etc/security/pam_env.conf. +# +# parsing /etc/environment needs "readenv=1" +session required pam_env.so readenv=1 +# locale variables are also kept into /etc/default/locale in etch +# reading this file *in addition to /etc/environment* does not hurt +session required pam_env.so readenv=1 envfile=/etc/default/locale + +# Standard Un*x authentication. +@include common-auth + +# This allows certain extra groups to be granted to a user +# based on things like time of day, tty, service, and user. +# Please edit /etc/security/group.conf to fit your needs +# (Replaces the `CONSOLE_GROUPS' option in login.defs) +auth optional pam_group.so + +# Uncomment and edit /etc/security/time.conf if you need to set +# time restraint on logins. +# (Replaces the `PORTTIME_CHECKS_ENAB' option from login.defs +# as well as /etc/porttime) +# account requisite pam_time.so + +# Uncomment and edit /etc/security/access.conf if you need to +# set access limits. +# (Replaces /etc/login.access file) +# account required pam_access.so + +# Sets up user limits according to /etc/security/limits.conf +# (Replaces the use of /etc/limits in old login) +session required pam_limits.so + +# Prints the last login info upon successful login +# (Replaces the `LASTLOG_ENAB' option from login.defs) +session optional pam_lastlog.so + +# Prints the message of the day upon successful login. +# (Replaces the `MOTD_FILE' option in login.defs) +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Prints the status of the user's mailbox upon successful login +# (Replaces the `MAIL_CHECK_ENAB' option from login.defs). +# +# This also defines the MAIL environment variable +# However, userdel also needs MAIL_DIR and MAIL_FILE variables +# in /etc/login.defs to make sure that removing a user +# also removes the user's mail spool file. +# See comments in /etc/login.defs +session optional pam_mail.so standard + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x account and session +@include common-account +@include common-session +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/radius_nss.conf b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/radius_nss.conf new file mode 100644 index 000000000000..8c31db9fbafe --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/radius_nss.conf @@ -0,0 +1,56 @@ +#THIS IS AN AUTO-GENERATED FILE +# Generated from: /usr/share/sonic/templates/radius_nss.conf.j2 +# RADIUS NSS Configuration File +# +# Debug: on|off|trace +# Default: off +# +# debug=on +debug=on + +# +# User Privilege: +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell + +# Eg: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/usr/bin/sonic-launch-shell +# user_priv=7;pw_info=netops;gid=999;group=docker;shell=/usr/bin/sonic-launch-shell +# user_priv=1;pw_info=operator;gid=100;group=docker;shell=/usr/bin/sonic-launch-shell +# + +# many_to_one: +# y: Map RADIUS users to one local user per privilege. +# n: Create local user account on first successful authentication. +# Default: n +# + +# Eg: +# many_to_one=y +# + +# unconfirmed_disallow: +# y: Do not allow unconfirmed users (users created before authentication) +# n: Allow unconfirmed users. +# Default: n + +# Eg: +# unconfirmed_disallow=y +# + +# unconfirmed_ageout: +# : Wait time before purging unconfirmed users +# Default: 600 +# + +# Eg: +# unconfirmed_ageout=900 +# + +# unconfirmed_regexp: +# : The RE to match the command line of processes for which the +# creation of unconfirmed users are to be allowed. +# Default: (.*: \[priv\])|(.*: \[accepted\]) +# where: is the unconfirmed user. +# \ No newline at end of file diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/sshd b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/sshd new file mode 100644 index 000000000000..c025af353d6f --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/sshd @@ -0,0 +1,55 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +@include common-auth-sonic + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so user_readenv=1 envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/sshd.old b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/sshd.old new file mode 100644 index 000000000000..d70b384bd9a3 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/sshd.old @@ -0,0 +1,55 @@ +# PAM configuration for the Secure Shell service + +# Standard Un*x authentication. +@include common-auth + +# Disallow non-root logins when /etc/nologin exists. +account required pam_nologin.so + +# Uncomment and edit /etc/security/access.conf if you need to set complex +# access limits that are hard to express in sshd_config. +# account required pam_access.so + +# Standard Un*x authorization. +@include common-account + +# SELinux needs to be the first session rule. This ensures that any +# lingering context has been cleared. Without this it is possible that a +# module could execute code in the wrong domain. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close + +# Set the loginuid process attribute. +session required pam_loginuid.so + +# Create a new session keyring. +session optional pam_keyinit.so force revoke + +# Standard Un*x session setup and teardown. +@include common-session + +# Print the message of the day upon successful login. +# This includes a dynamically generated part from /run/motd.dynamic +# and a static (admin-editable) part from /etc/motd. +session optional pam_motd.so motd=/run/motd.dynamic +session optional pam_motd.so noupdate + +# Print the status of the user's mailbox upon successful login. +session optional pam_mail.so standard noenv # [1] + +# Set up user limits from /etc/security/limits.conf. +session required pam_limits.so + +# Read environment variables from /etc/environment and +# /etc/security/pam_env.conf. +session required pam_env.so # [1] +# In Debian 4.0 (etch), locale-related environment variables were moved to +# /etc/default/locale, so read that as well. +session required pam_env.so user_readenv=1 envfile=/etc/default/locale + +# SELinux needs to intervene at login time to ensure that the process starts +# in the proper default security context. Only sessions which are intended +# to run in the user's context should be run after this. +session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open + +# Standard Un*x password updating. +@include common-password diff --git a/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/tacplus_nss.conf b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/tacplus_nss.conf new file mode 100644 index 000000000000..fe506941f98d --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/sample_output/RADIUS/tacplus_nss.conf @@ -0,0 +1,23 @@ +# Configuration for libnss-tacplus + +# debug - If you want to open debug log, set it on +# Default: off +# debug=on +debug=on + +# src_ip - set source address of TACACS+ protocol packets +# Default: None (auto source ip address) +# src_ip=2.2.2.2 + +# server - set ip address, tcp port, secret string and timeout for TACACS+ servers +# Default: None (no TACACS+ server) +# server=1.1.1.1:49,secret=test,timeout=3 + +# user_priv - set the map between TACACS+ user privilege and local user's passwd +# Default: +# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/bin/bash +# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/bin/bash + +# many_to_one - create one local user for many TACACS+ users which has the same privilege +# Default: many_to_one=n +# many_to_one=y diff --git a/src/sonic-host-services/tests/hostcfgd/test_radius_vectors.py b/src/sonic-host-services/tests/hostcfgd/test_radius_vectors.py new file mode 100644 index 000000000000..df10499e1a23 --- /dev/null +++ b/src/sonic-host-services/tests/hostcfgd/test_radius_vectors.py @@ -0,0 +1,181 @@ +from unittest.mock import call + +""" + hostcfgd test radius vector +""" +HOSTCFGD_TEST_RADIUS_VECTOR = [ + [ + "RADIUS", + { + "config_db": { + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "radius,local", + "debug": "True", + } + }, + "RADIUS": { + "global": { + "nas_ip": "10.10.10.10", + "auth_port": "1645", + "auth_type": "mschapv2", + "retransmit": "2", + "timeout": "3", + "passkey": "pass", + } + }, + "RADIUS_SERVER": { + "10.10.10.1": { + "auth_type": "pap", + "retransmit": "1", + "timeout": "1", + "passkey": "pass1", + }, + "10.10.10.2": { + "auth_type": "chap", + "retransmit": "2", + "timeout": "2", + "passkey": "pass2", + } + }, + }, + "expected_config_db": { + "DEVICE_METADATA": { + "localhost": { + "hostname": "radius", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "AAA": { + "authentication": { + "login": "radius,local", + "debug": "True", + } + }, + "RADIUS": { + "global": { + "nas_ip": "10.10.10.10", + "auth_port": "1645", + "auth_type": "mschapv2", + "retransmit": "2", + "timeout": "3", + "passkey": "pass", + } + }, + "RADIUS_SERVER": { + "10.10.10.1": { + "auth_type": "pap", + "retransmit": "1", + "timeout": "1", + "passkey": "pass1", + }, + "10.10.10.2": { + "auth_type": "chap", + "retransmit": "2", + "timeout": "2", + "passkey": "pass2", + } + }, + }, + "expected_subprocess_calls": [ + call("service aaastatsd start", shell=True), + ], + } + ], + [ + "LOCAL", + { + "config_db": { + "DEVICE_METADATA": { + "localhost": { + "hostname": "local", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "KDUMP": { + "config": { + "enabled": "false", + "num_dumps": "3", + "memory": "0M-2G:256M,2G-4G:320M,4G-8G:384M,8G-:448M" + } + }, + "AAA": { + "authentication": { + "login": "local", + "debug": "True", + } + }, + }, + "expected_config_db": { + "DEVICE_METADATA": { + "localhost": { + "hostname": "local", + } + }, + "FEATURE": { + "dhcp_relay": { + "auto_restart": "enabled", + "has_global_scope": "True", + "has_per_asic_scope": "False", + "has_timer": "False", + "high_mem_alert": "disabled", + "set_owner": "kube", + "state": "enabled" + }, + }, + "AAA": { + "authentication": { + "login": "local", + "debug": "True", + } + }, + }, + "expected_subprocess_calls": [ + call("service aaastatsd start", shell=True), + ], + }, + ], +]