diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index 232600b48..22b50ce2a 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -1,206 +1,203 @@
 #!/bin/bash
 
 # Turn off Debian default for %sudo
 sed -i -e '/^%sudo/d' /etc/sudoers || true
 
 # Add minion user for salt-minion
 if ! grep -q '^minion' /etc/passwd; then
     adduser --quiet --firstuid 100 --system --disabled-login --ingroup vyattacfg \
         --gecos "salt minion user" --shell /bin/vbash minion
     adduser --quiet minion frrvty
     adduser --quiet minion sudo
     adduser --quiet minion adm
     adduser --quiet minion dip
     adduser --quiet minion disk
     adduser --quiet minion users
     adduser --quiet minion frr
 fi
 
 # OpenVPN should get its own user
 if ! grep -q '^openvpn' /etc/passwd; then
     adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn
 fi
 
 # Enable 2FA/MFA support for SSH and local logins
 for file in /etc/pam.d/sshd /etc/pam.d/login
 do
     PAM_CONFIG="# Check 2FA/MFA authentication token if enabled (per user)\nauth       required     pam_google_authenticator.so nullok forward_pass\n"
     grep -qF -- "pam_google_authenticator.so" $file || \
     sed -i "/^# Standard Un\*x authentication\./i${PAM_CONFIG}" $file
 done
 
-# We do not make use of a TACACS UNIX group - drop it
-if grep -q '^tacacs' /etc/group; then
-    delgroup tacacs
-fi
-
-# Both RADIUS and TACACS users belong to aaa group - this must be added first
-if ! grep -q '^aaa' /etc/group; then
-    addgroup --firstgid 1000 --quiet aaa
+# We need to have a group for RADIUS service users to use it inside PAM rules
+if ! grep -q '^radius' /etc/group; then
+    addgroup --firstgid 1000 --quiet radius
 fi
 
 # Remove TACACS user added by base package - we use our own UID range and group
 # assignments - see below
 if grep -q '^tacacs' /etc/passwd; then
     if [ $(id -u tacacs0) -ge 1000 ]; then
         level=0
         vyos_group=vyattaop
         while [ $level -lt 16 ]; do
             userdel tacacs${level} || true
             rm -rf /home/tacacs${level} || true
             level=$(( level+1 ))
         done 2>&1
     fi
 fi
 
+# Remove TACACS+ PAM default profile
+if [[ -e /usr/share/pam-configs/tacplus ]]; then
+    rm /usr/share/pam-configs/tacplus
+fi
+
 # Add TACACS system users required for TACACS based system authentication
 if ! grep -q '^tacacs' /etc/passwd; then
     # Add the tacacs group and all 16 possible tacacs privilege-level users to
     # the password file, home directories, etc. The accounts are not enabled
     # for local login, since they are only used to provide uid/gid/homedir for
     # the mapped TACACS+ logins (and lookups against them). The tacacs15 user
     # is also added to the sudo group, and vyattacfg group rather than vyattaop
     # (used for tacacs0-14).
     level=0
     vyos_group=vyattaop
     while [ $level -lt 16 ]; do
-        adduser --quiet --system --firstuid 900 --disabled-login --ingroup users \
+        adduser --quiet --system --firstuid 900 --disabled-login --ingroup tacacs \
             --no-create-home --gecos "TACACS+ mapped user at privilege level ${level}" \
             --shell /bin/vbash tacacs${level}
         adduser --quiet tacacs${level} frrvty
         adduser --quiet tacacs${level} adm
         adduser --quiet tacacs${level} dip
         adduser --quiet tacacs${level} users
-        adduser --quiet tacacs${level} aaa
         if [ $level -lt 15 ]; then
             adduser --quiet tacacs${level} vyattaop
             adduser --quiet tacacs${level} operator
         else
             adduser --quiet tacacs${level} vyattacfg
             adduser --quiet tacacs${level} sudo
             adduser --quiet tacacs${level} disk
             adduser --quiet tacacs${level} frr
         fi
         level=$(( level+1 ))
     done 2>&1 | grep -v 'User tacacs${level} already exists'
 fi
 
 # Add RADIUS operator user for RADIUS authenticated users to map to
 if ! grep -q '^radius_user' /etc/passwd; then
-    adduser --quiet --firstuid 1000 --disabled-login --ingroup users \
+    adduser --quiet --firstuid 1000 --disabled-login --ingroup radius \
         --no-create-home --gecos "RADIUS mapped user at privilege level operator" \
         --shell /sbin/radius_shell radius_user
     adduser --quiet radius_user frrvty
     adduser --quiet radius_user vyattaop
     adduser --quiet radius_user operator
     adduser --quiet radius_user adm
     adduser --quiet radius_user dip
     adduser --quiet radius_user users
-    adduser --quiet radius_user aaa
 fi
 
 # Add RADIUS admin user for RADIUS authenticated users to map to
 if ! grep -q '^radius_priv_user' /etc/passwd; then
-    adduser --quiet --firstuid 1000 --disabled-login --ingroup users \
+    adduser --quiet --firstuid 1000 --disabled-login --ingroup radius \
         --no-create-home --gecos "RADIUS mapped user at privilege level admin" \
         --shell /sbin/radius_shell radius_priv_user
     adduser --quiet radius_priv_user frrvty
     adduser --quiet radius_priv_user vyattacfg
     adduser --quiet radius_priv_user sudo
     adduser --quiet radius_priv_user adm
     adduser --quiet radius_priv_user dip
     adduser --quiet radius_priv_user disk
     adduser --quiet radius_priv_user users
     adduser --quiet radius_priv_user frr
-    adduser --quiet radius_priv_user aaa
 fi
 
 # add hostsd group for vyos-hostsd
 if ! grep -q '^hostsd' /etc/group; then
     addgroup --quiet --system hostsd
 fi
 
 # add dhcpd user for dhcp-server
 if ! grep -q '^dhcpd' /etc/passwd; then
     adduser --quiet --system --disabled-login --no-create-home --home /run/dhcp-server dhcpd
     adduser --quiet dhcpd hostsd
 fi
 
 # ensure the proxy user has a proper shell
 chsh -s /bin/sh proxy
 
 # create /opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script
 PRECONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script
 if [ ! -x $PRECONFIG_SCRIPT ]; then
     mkdir -p $(dirname $PRECONFIG_SCRIPT)
     touch $PRECONFIG_SCRIPT
     chmod 755 $PRECONFIG_SCRIPT
     cat <<EOF >>$PRECONFIG_SCRIPT
 #!/bin/sh
 # This script is executed at boot time before VyOS configuration is applied.
 # Any modifications required to work around unfixed bugs or use
 # services not available through the VyOS CLI system can be placed here.
 
 EOF
 fi
 
 # create /opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script
 POSTCONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script
 if [ ! -x $POSTCONFIG_SCRIPT ]; then
     mkdir -p $(dirname $POSTCONFIG_SCRIPT)
     touch $POSTCONFIG_SCRIPT
     chmod 755 $POSTCONFIG_SCRIPT
     cat <<EOF >>$POSTCONFIG_SCRIPT
 #!/bin/sh
 # This script is executed at boot time after VyOS configuration is fully applied.
 # Any modifications required to work around unfixed bugs
 # or use services not available through the VyOS CLI system can be placed here.
 
 EOF
 fi
 
 # symlink destination is deleted during ISO assembly - this generates some noise
 # when the system boots: systemd-sysv-generator[1881]: stat() failed on
 # /etc/init.d/README, ignoring: No such file or directory. Thus we simply drop
 # the file.
 if [ -L /etc/init.d/README ]; then
     rm -f /etc/init.d/README
 fi
 
 # Remove unwanted daemon files from /etc
 # conntackd
 # pmacct
 # fastnetmon
 # ntp
 DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd
         /etc/default/pmacctd /etc/pmacct
         /etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf
         /etc/ntp.conf /etc/default/ssh
         /etc/powerdns /etc/default/pdns-recursor
         /etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns"
 for tmp in $DELETE; do
     if [ -e ${tmp} ]; then
         rm -rf ${tmp}
     fi
 done
 
 # Remove logrotate items controlled via CLI and VyOS defaults
 sed -i '/^\/var\/log\/messages$/d' /etc/logrotate.d/rsyslog
 sed -i '/^\/var\/log\/auth.log$/d' /etc/logrotate.d/rsyslog
 
 # Fix FRR pam.d "vtysh_pam" vtysh_pam: Failed in account validation T5110
 if test -f /etc/pam.d/frr; then
     if grep -q 'pam_rootok.so' /etc/pam.d/frr; then
         sed -i -re 's/rootok/permit/' /etc/pam.d/frr
     fi
 fi
 
 # Enable Cloud-init pre-configuration service
 systemctl enable vyos-config-cloud-init.service
 
 # Generate API GraphQL schema
 /usr/libexec/vyos/services/api/graphql/generate/generate_schema.py
 
 # Update XML cache
 python3 /usr/lib/python3/dist-packages/vyos/xml_ref/update_cache.py
 
diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst
index 12866cd55..fbfc85566 100644
--- a/debian/vyos-1x.preinst
+++ b/debian/vyos-1x.preinst
@@ -1,13 +1,11 @@
 dpkg-divert --package vyos-1x --add --no-rename /etc/securetty
 dpkg-divert --package vyos-1x --add --no-rename /etc/security/capability.conf
 dpkg-divert --package vyos-1x --add --no-rename /lib/systemd/system/lcdproc.service
 dpkg-divert --package vyos-1x --add --no-rename /etc/logrotate.d/conntrackd
-dpkg-divert --package vyos-1x --add --no-rename /usr/share/pam-configs/radius
-dpkg-divert --package vyos-1x --add --no-rename /usr/share/pam-configs/tacplus
 dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.conf
 dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.bashrc
 dpkg-divert --package vyos-1x --add --no-rename /etc/skel/.profile
 dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplugd.conf
 dpkg-divert --package vyos-1x --add --no-rename /etc/netplug/netplug
 dpkg-divert --package vyos-1x --add --no-rename /etc/rsyslog.d/45-frr.conf
 dpkg-divert --package vyos-1x --add --no-rename /lib/udev/rules.d/99-systemd.rules
diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
index ac0950ae8..e454b9025 100644
--- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
@@ -1,31 +1,51 @@
 <!-- include start from radius-server-ipv4-ipv6.xml.i -->
 <node name="radius">
   <properties>
     <help>RADIUS based user authentication</help>
   </properties>
   <children>
     <tagNode name="server">
       <properties>
         <help>RADIUS server configuration</help>
         <valueHelp>
           <format>ipv4</format>
           <description>RADIUS server IPv4 address</description>
         </valueHelp>
         <valueHelp>
           <format>ipv6</format>
           <description>RADIUS server IPv6 address</description>
         </valueHelp>
         <constraint>
           <validator name="ip-address"/>
         </constraint>
       </properties>
       <children>
         #include <include/generic-disable-node.xml.i>
         #include <include/radius-server-key.xml.i>
         #include <include/radius-server-auth-port.xml.i>
       </children>
     </tagNode>
     #include <include/source-address-ipv4-ipv6-multi.xml.i>
+    <leafNode name="security-mode">
+      <properties>
+        <help>Security mode for RADIUS authentication</help>
+        <completionHelp>
+          <list>mandatory optional</list>
+        </completionHelp>
+        <valueHelp>
+          <format>mandatory</format>
+          <description>Deny access immediately if RADIUS answers with Access-Reject</description>
+        </valueHelp>
+        <valueHelp>
+          <format>optional</format>
+          <description>Pass to the next authentication method if RADIUS answers with Access-Reject</description>
+        </valueHelp>
+        <constraint>
+          <regex>(mandatory|optional)</regex>
+        </constraint>
+      </properties>
+      <defaultValue>optional</defaultValue>
+    </leafNode>
   </children>
 </node>
 <!-- include end -->
diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in
index a0eda9045..be0145b4f 100644
--- a/interface-definitions/system-login.xml.in
+++ b/interface-definitions/system-login.xml.in
@@ -1,282 +1,302 @@
 <?xml version="1.0"?>
 <interfaceDefinition>
   <node name="system">
     <children>
       <node name="login" owner="${vyos_conf_scripts_dir}/system-login.py">
         <properties>
           <help>System User Login Configuration</help>
           <priority>400</priority>
         </properties>
         <children>
           <tagNode name="user">
             <properties>
               <help>Local user account information</help>
               <constraint>
                 #include <include/constraint/login-username.xml.i>
               </constraint>
               <constraintErrorMessage>Username contains illegal characters or\nexceeds 100 character limitation.</constraintErrorMessage>
             </properties>
             <children>
               <node name="authentication">
                 <properties>
                   <help>Authentication settings</help>
                 </properties>
                 <children>
                   <leafNode name="encrypted-password">
                     <properties>
                       <help>Encrypted password</help>
                       <constraint>
                         <regex>(\*|\!)</regex>
                         <regex>[a-zA-Z0-9\.\/]{13}</regex>
                         <regex>\$1\$[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{22}</regex>
                         <regex>\$5\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{43}</regex>
                         <regex>\$6\$(rounds=[0-9]+\$)?[a-zA-Z0-9\./]*\$[a-zA-Z0-9\./]{86}</regex>
                       </constraint>
                       <constraintErrorMessage>Invalid encrypted password for $VAR(../../@).</constraintErrorMessage>
                     </properties>
                     <defaultValue>!</defaultValue>
                   </leafNode>
                   <node name="otp">
                     <properties>
                       <help>One-Time-Pad (two-factor) authentication parameters</help>
                     </properties>
                     <children>
                       <leafNode name="rate-limit">
                         <properties>
                           <help>Limit number of logins (rate-limit) per rate-time</help>
                           <valueHelp>
                             <format>u32:1-10</format>
                             <description>Number of attempts</description>
                           </valueHelp>
                           <constraint>
                             <validator name="numeric" argument="--range 1-10"/>
                           </constraint>
                           <constraintErrorMessage>Number of login attempts must me between 1 and 10</constraintErrorMessage>
                         </properties>
                         <defaultValue>3</defaultValue>
                       </leafNode>
                       <leafNode name="rate-time">
                         <properties>
                           <help>Limit number of logins (rate-limit) per rate-time</help>
                           <valueHelp>
                             <format>u32:15-600</format>
                             <description>Time interval</description>
                           </valueHelp>
                           <constraint>
                             <validator name="numeric" argument="--range 15-600"/>
                           </constraint>
                           <constraintErrorMessage>Rate limit time interval must be between 15 and 600 seconds</constraintErrorMessage>
                         </properties>
                         <defaultValue>30</defaultValue>
                       </leafNode>
                       <leafNode name="window-size">
                         <properties>
                           <help>Set window of concurrently valid codes</help>
                           <valueHelp>
                             <format>u32:1-21</format>
                             <description>Window size</description>
                           </valueHelp>
                           <constraint>
                             <validator name="numeric" argument="--range 1-21"/>
                           </constraint>
                           <constraintErrorMessage>Window of concurrently valid codes must be between 1 and 21</constraintErrorMessage>
                         </properties>
                         <defaultValue>3</defaultValue>
                       </leafNode>
                       <leafNode name="key">
                         <properties>
                           <help>Key/secret the token algorithm (see RFC4226)</help>
                           <valueHelp>
                             <format>txt</format>
                             <description>Base32 encoded key/token</description>
                           </valueHelp>
                           <constraint>
                             <regex>[a-zA-Z2-7]{26,10000}</regex>
                           </constraint>
                           <constraintErrorMessage>Key must only include base32 characters and be at least 26 characters long</constraintErrorMessage>
                         </properties>
                       </leafNode>
                     </children>
                   </node>
                   <leafNode name="plaintext-password">
                     <properties>
                       <help>Plaintext password used for encryption</help>
                     </properties>
                   </leafNode>
                   <tagNode name="public-keys">
                     <properties>
                       <help>Remote access public keys</help>
                       <valueHelp>
                         <format>txt</format>
                         <description>Key identifier used by ssh-keygen (usually of form user@host)</description>
                       </valueHelp>
                     </properties>
                     <children>
                       <leafNode name="key">
                         <properties>
                           <help>Public key value (Base64 encoded)</help>
                           <constraint>
                             <validator name="base64"/>
                           </constraint>
                         </properties>
                       </leafNode>
                       <leafNode name="options">
                         <properties>
                           <help>Optional public key options</help>
                         </properties>
                       </leafNode>
                       <leafNode name="type">
                         <properties>
                           <help>SSH public key type</help>
                           <completionHelp>
                             <list>ssh-dss ssh-rsa ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 ssh-ed25519 sk-ecdsa-sha2-nistp256@openssh.com sk-ssh-ed25519@openssh.com</list>
                           </completionHelp>
                           <valueHelp>
                             <format>ssh-dss</format>
                             <description>Digital Signature Algorithm (DSA) key support</description>
                           </valueHelp>
                           <valueHelp>
                             <format>ssh-rsa</format>
                             <description>Key pair based on RSA algorithm</description>
                           </valueHelp>
                           <valueHelp>
                             <format>ecdsa-sha2-nistp256</format>
                             <description>Elliptic Curve DSA with NIST P-256 curve</description>
                           </valueHelp>
                           <valueHelp>
                             <format>ecdsa-sha2-nistp384</format>
                             <description>Elliptic Curve DSA with NIST P-384 curve</description>
                           </valueHelp>
                           <valueHelp>
                             <format>ecdsa-sha2-nistp521</format>
                             <description>Elliptic Curve DSA with NIST P-521 curve</description>
                           </valueHelp>
                           <valueHelp>
                             <format>ssh-ed25519</format>
                             <description>Edwards-curve DSA with elliptic curve 25519</description>
                           </valueHelp>
                           <valueHelp>
                             <format>sk-ecdsa-sha2-nistp256@openssh.com</format>
                             <description>Elliptic Curve DSA security key</description>
                           </valueHelp>
                           <valueHelp>
                             <format>sk-ssh-ed25519@openssh.com</format>
                             <description>Elliptic curve 25519 security key</description>
                           </valueHelp>
                           <constraint>
                             <regex>(ssh-dss|ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519|sk-ecdsa-sha2-nistp256@openssh.com|sk-ssh-ed25519@openssh.com)</regex>
                           </constraint>
                         </properties>
                       </leafNode>
                     </children>
                   </tagNode>
                 </children>
               </node>
               <leafNode name="full-name">
                 <properties>
                   <help>Full name of the user (use quotes for names with spaces)</help>
                   <constraint>
                     <regex>[^:]*</regex>
                   </constraint>
                   <constraintErrorMessage>Cannot use ':' in full name</constraintErrorMessage>
                 </properties>
               </leafNode>
               <leafNode name="home-directory">
                 <properties>
                   <help>Home directory</help>
                   <valueHelp>
                     <format>txt</format>
                     <description>Path to home directory</description>
                   </valueHelp>
                   <constraint>
                     <regex>\/$|(\/[a-zA-Z_0-9-.]+)+</regex>
                   </constraint>
                 </properties>
               </leafNode>
             </children>
           </tagNode>
           #include <include/radius-server-ipv4-ipv6.xml.i>
           <node name="radius">
             <children>
               <tagNode name="server">
                 <children>
                   #include <include/radius-timeout.xml.i>
                   <leafNode name="priority">
                     <properties>
                       <help>Server priority</help>
                       <valueHelp>
                         <format>u32:1-255</format>
                         <description>Server priority</description>
                       </valueHelp>
                       <constraint>
                         <validator name="numeric" argument="--range 1-255"/>
                       </constraint>
                     </properties>
                     <defaultValue>255</defaultValue>
                   </leafNode>
                 </children>
               </tagNode>
               #include <include/interface/vrf.xml.i>
             </children>
           </node>
           <node name="tacacs">
             <properties>
               <help>TACACS+ based user authentication</help>
             </properties>
             <children>
               <tagNode name="server">
                 <properties>
                   <help>TACACS+ server configuration</help>
                   <valueHelp>
                     <format>ipv4</format>
                     <description>TACACS+ server IPv4 address</description>
                   </valueHelp>
                   <constraint>
                     <validator name="ipv4-address"/>
                   </constraint>
                 </properties>
                 <children>
                   #include <include/generic-disable-node.xml.i>
                   #include <include/radius-server-key.xml.i>
                   #include <include/port-number.xml.i>
                   <leafNode name="port">
                     <defaultValue>49</defaultValue>
                   </leafNode>
                 </children>
               </tagNode>
+              <leafNode name="security-mode">
+                <properties>
+                  <help>Security mode for TACACS+ authentication</help>
+                  <completionHelp>
+                    <list>mandatory optional</list>
+                  </completionHelp>
+                  <valueHelp>
+                    <format>mandatory</format>
+                    <description>Deny access immediately if TACACS+ answers with REJECT</description>
+                  </valueHelp>
+                  <valueHelp>
+                    <format>optional</format>
+                    <description>Pass to the next authentication method if TACACS+ answers with REJECT</description>
+                  </valueHelp>
+                  <constraint>
+                    <regex>(mandatory|optional)</regex>
+                  </constraint>
+                </properties>
+                <defaultValue>optional</defaultValue>
+              </leafNode>
               #include <include/source-address-ipv4.xml.i>
               #include <include/radius-timeout.xml.i>
               #include <include/interface/vrf.xml.i>
             </children>
           </node>
           <leafNode name="max-login-session">
             <properties>
               <help>Maximum number of all login sessions</help>
               <valueHelp>
                 <format>u32:1-65536</format>
                 <description>Maximum number of all login sessions</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 1-65536"/>
               </constraint>
               <constraintErrorMessage>Maximum logins must be between 1 and 65536</constraintErrorMessage>
             </properties>
           </leafNode>
           <leafNode name="timeout">
             <properties>
               <help>Session timeout</help>
               <valueHelp>
                 <format>u32:5-604800</format>
                 <description>Session timeout in seconds</description>
               </valueHelp>
               <constraint>
                 <validator name="numeric" argument="--range 5-604800"/>
               </constraint>
               <constraintErrorMessage>Timeout must be between 5 and 604800 seconds</constraintErrorMessage>
             </properties>
           </leafNode>
         </children>
       </node>
     </children>
   </node>
 </interfaceDefinition>
diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py
index 02c97afaa..87a269499 100755
--- a/src/conf_mode/system-login.py
+++ b/src/conf_mode/system-login.py
@@ -1,403 +1,412 @@
 #!/usr/bin/env python3
 #
 # Copyright (C) 2020-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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, see <http://www.gnu.org/licenses/>.
 
 import os
 
 from passlib.hosts import linux_context
 from psutil import users
 from pwd import getpwall
 from pwd import getpwnam
 from sys import exit
 from time import sleep
 
 from vyos.config import Config
 from vyos.configverify import verify_vrf
 from vyos.defaults import directories
 from vyos.template import render
 from vyos.template import is_ipv4
 from vyos.utils.dict import dict_search
 from vyos.utils.process import cmd
 from vyos.utils.process import call
 from vyos.utils.process import rc_cmd
 from vyos.utils.process import run
 from vyos.utils.process import DEVNULL
 from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
 autologout_file = "/etc/profile.d/autologout.sh"
 limits_file = "/etc/security/limits.d/10-vyos.conf"
 radius_config_file = "/etc/pam_radius_auth.conf"
 tacacs_pam_config_file = "/etc/tacplus_servers"
 tacacs_nss_config_file = "/etc/tacplus_nss.conf"
 nss_config_file = "/etc/nsswitch.conf"
 
 # Minimum UID used when adding system users
 MIN_USER_UID: int = 1000
 # Maximim UID used when adding system users
 MAX_USER_UID: int = 59999
 # LOGIN_TIMEOUT from /etc/loign.defs minus 10 sec
 MAX_RADIUS_TIMEOUT: int = 50
 # MAX_RADIUS_TIMEOUT divided by 2 sec (minimum recomended timeout)
 MAX_RADIUS_COUNT: int = 8
 # Maximum number of supported TACACS servers
 MAX_TACACS_COUNT: int = 8
 
 # List of local user accounts that must be preserved
 SYSTEM_USER_SKIP_LIST: list = ['radius_user', 'radius_priv_user', 'tacacs0', 'tacacs1',
                               'tacacs2', 'tacacs3', 'tacacs4', 'tacacs5', 'tacacs6',
                               'tacacs7', 'tacacs8', 'tacacs9', 'tacacs10',' tacacs11',
                               'tacacs12', 'tacacs13', 'tacacs14', 'tacacs15']
 
 def get_local_users():
     """Return list of dynamically allocated users (see Debian Policy Manual)"""
     local_users = []
     for s_user in getpwall():
         if getpwnam(s_user.pw_name).pw_uid < MIN_USER_UID:
             continue
         if getpwnam(s_user.pw_name).pw_uid > MAX_USER_UID:
             continue
         if s_user.pw_name in SYSTEM_USER_SKIP_LIST:
             continue
         local_users.append(s_user.pw_name)
 
     return local_users
 
 def get_shadow_password(username):
     with open('/etc/shadow') as f:
         for user in f.readlines():
             items = user.split(":")
             if username == items[0]:
                 return items[1]
     return None
 
 def get_config(config=None):
     if config:
         conf = config
     else:
         conf = Config()
     base = ['system', 'login']
     login = conf.get_config_dict(base, key_mangling=('-', '_'),
                                  no_tag_node_value_mangle=True,
                                  get_first_key=True,
                                  with_recursive_defaults=True)
 
     # users no longer existing in the running configuration need to be deleted
     local_users = get_local_users()
     cli_users = []
     if 'user' in login:
         cli_users = list(login['user'])
 
     # prune TACACS global defaults if not set by user
     if login.from_defaults(['tacacs']):
         del login['tacacs']
+    # same for RADIUS
+    if login.from_defaults(['radius']):
+        del login['radius']
 
     # create a list of all users, cli and users
     all_users = list(set(local_users + cli_users))
     # We will remove any normal users that dos not exist in the current
     # configuration. This can happen if user is added but configuration was not
     # saved and system is rebooted.
     rm_users = [tmp for tmp in all_users if tmp not in cli_users]
     if rm_users: login.update({'rm_users' : rm_users})
 
     return login
 
 def verify(login):
     if 'rm_users' in login:
         # This check is required as the script is also executed from vyos-router
         # init script and there is no SUDO_USER environment variable available
         # during system boot.
         if 'SUDO_USER' in os.environ:
             cur_user = os.environ['SUDO_USER']
             if cur_user in login['rm_users']:
                 raise ConfigError(f'Attempting to delete current user: {cur_user}')
 
     if 'user' in login:
         system_users = getpwall()
         for user, user_config in login['user'].items():
             # Linux system users range up until UID 1000, we can not create a
             # VyOS CLI user which already exists as system user
             for s_user in system_users:
                 if s_user.pw_name == user and s_user.pw_uid < MIN_USER_UID:
                     raise ConfigError(f'User "{user}" can not be created, conflict with local system account!')
 
             for pubkey, pubkey_options in (dict_search('authentication.public_keys', user_config) or {}).items():
                 if 'type' not in pubkey_options:
                     raise ConfigError(f'Missing type for public-key "{pubkey}"!')
                 if 'key' not in pubkey_options:
                     raise ConfigError(f'Missing key for public-key "{pubkey}"!')
 
     if {'radius', 'tacacs'} <= set(login):
         raise ConfigError('Using both RADIUS and TACACS at the same time is not supported!')
 
     # At lease one RADIUS server must not be disabled
     if 'radius' in login:
         if 'server' not in login['radius']:
             raise ConfigError('No RADIUS server defined!')
         sum_timeout: int = 0
         radius_servers_count: int = 0
         fail = True
         for server, server_config in dict_search('radius.server', login).items():
             if 'key' not in server_config:
                 raise ConfigError(f'RADIUS server "{server}" requires key!')
             if 'disable' not in server_config:
                 sum_timeout += int(server_config['timeout'])
                 radius_servers_count += 1
                 fail = False
 
         if fail:
             raise ConfigError('All RADIUS servers are disabled')
 
         if radius_servers_count > MAX_RADIUS_COUNT:
             raise ConfigError(f'Number of RADIUS servers exceeded maximum of {MAX_RADIUS_COUNT}!')
 
         if sum_timeout > MAX_RADIUS_TIMEOUT:
             raise ConfigError('Sum of RADIUS servers timeouts '
                               'has to be less or eq 50 sec')
 
         verify_vrf(login['radius'])
 
         if 'source_address' in login['radius']:
             ipv4_count = 0
             ipv6_count = 0
             for address in login['radius']['source_address']:
                 if is_ipv4(address): ipv4_count += 1
                 else:                ipv6_count += 1
 
             if ipv4_count > 1:
                 raise ConfigError('Only one IPv4 source-address can be set!')
             if ipv6_count > 1:
                 raise ConfigError('Only one IPv6 source-address can be set!')
 
     if 'tacacs' in login:
         tacacs_servers_count: int = 0
         fail = True
         for server, server_config in dict_search('tacacs.server', login).items():
             if 'key' not in server_config:
                 raise ConfigError(f'TACACS server "{server}" requires key!')
             if 'disable' not in server_config:
                 tacacs_servers_count += 1
                 fail = False
 
         if fail:
             raise ConfigError('All RADIUS servers are disabled')
 
         if tacacs_servers_count > MAX_TACACS_COUNT:
             raise ConfigError(f'Number of TACACS servers exceeded maximum of {MAX_TACACS_COUNT}!')
 
         verify_vrf(login['tacacs'])
 
     if 'max_login_session' in login and 'timeout' not in login:
         raise ConfigError('"login timeout" must be configured!')
 
     return None
 
 
 def generate(login):
     # calculate users encrypted password
     if 'user' in login:
         for user, user_config in login['user'].items():
             tmp = dict_search('authentication.plaintext_password', user_config)
             if tmp:
                 encrypted_password = linux_context.hash(tmp)
                 login['user'][user]['authentication']['encrypted_password'] = encrypted_password
                 del login['user'][user]['authentication']['plaintext_password']
 
                 # remove old plaintext password and set new encrypted password
                 env = os.environ.copy()
                 env['vyos_libexec_dir'] = directories['base']
 
                 # Set default commands for re-adding user with encrypted password
                 del_user_plain = f"system login user {user} authentication plaintext-password"
                 add_user_encrypt = f"system login user {user} authentication encrypted-password '{encrypted_password}'"
 
                 lvl = env['VYATTA_EDIT_LEVEL']
                 # We're in config edit level, for example "edit system login"
                 # Change default commands for re-adding user with encrypted password
                 if lvl != '/':
                     # Replace '/system/login' to 'system login'
                     lvl = lvl.strip('/').split('/')
                     # Convert command str to list
                     del_user_plain = del_user_plain.split()
                     # New command exclude level, for example "edit system login"
                     del_user_plain = del_user_plain[len(lvl):]
                     # Convert string to list
                     del_user_plain = " ".join(del_user_plain)
 
                     add_user_encrypt = add_user_encrypt.split()
                     add_user_encrypt = add_user_encrypt[len(lvl):]
                     add_user_encrypt = " ".join(add_user_encrypt)
 
                 ret, out = rc_cmd(f"/opt/vyatta/sbin/my_delete {del_user_plain}", env=env)
                 if ret: raise ConfigError(out)
                 ret, out = rc_cmd(f"/opt/vyatta/sbin/my_set {add_user_encrypt}", env=env)
                 if ret: raise ConfigError(out)
             else:
                 try:
                     if get_shadow_password(user) == dict_search('authentication.encrypted_password', user_config):
                         # If the current encrypted bassword matches the encrypted password
                         # from the config - do not update it. This will remove the encrypted
                         # value from the system logs.
                         #
                         # The encrypted password will be set only once during the first boot
                         # after an image upgrade.
                         del login['user'][user]['authentication']['encrypted_password']
                 except:
                     pass
 
     ### RADIUS based user authentication
     if 'radius' in login:
         render(radius_config_file, 'login/pam_radius_auth.conf.j2', login,
                    permission=0o600, user='root', group='root')
     else:
         if os.path.isfile(radius_config_file):
             os.unlink(radius_config_file)
 
     ### TACACS+ based user authentication
     if 'tacacs' in login:
         render(tacacs_pam_config_file, 'login/tacplus_servers.j2', login,
                    permission=0o644, user='root', group='root')
         render(tacacs_nss_config_file, 'login/tacplus_nss.conf.j2', login,
                    permission=0o644, user='root', group='root')
     else:
         if os.path.isfile(tacacs_pam_config_file):
             os.unlink(tacacs_pam_config_file)
         if os.path.isfile(tacacs_nss_config_file):
             os.unlink(tacacs_nss_config_file)
 
 
 
     # NSS must always be present on the system
     render(nss_config_file, 'login/nsswitch.conf.j2', login,
                permission=0o644, user='root', group='root')
 
     # /etc/security/limits.d/10-vyos.conf
     if 'max_login_session' in login:
         render(limits_file, 'login/limits.j2', login,
                    permission=0o644, user='root', group='root')
     else:
         if os.path.isfile(limits_file):
             os.unlink(limits_file)
 
     if 'timeout' in login:
         render(autologout_file, 'login/autologout.j2', login,
                    permission=0o755, user='root', group='root')
     else:
         if os.path.isfile(autologout_file):
             os.unlink(autologout_file)
 
     return None
 
 
 def apply(login):
     if 'user' in login:
         for user, user_config in login['user'].items():
             # make new user using vyatta shell and make home directory (-m),
             # default group of 100 (users)
             command = 'useradd --create-home --no-user-group '
             # check if user already exists:
             if user in get_local_users():
                 # update existing account
                 command = 'usermod'
 
             # all accounts use /bin/vbash
             command += ' --shell /bin/vbash'
             # we need to use '' quotes when passing formatted data to the shell
             # else it will not work as some data parts are lost in translation
             tmp = dict_search('authentication.encrypted_password', user_config)
             if tmp: command += f" --password '{tmp}'"
 
             tmp = dict_search('full_name', user_config)
             if tmp: command += f" --comment '{tmp}'"
 
             tmp = dict_search('home_directory', user_config)
             if tmp: command += f" --home '{tmp}'"
             else: command += f" --home '/home/{user}'"
 
             command += f' --groups frr,frrvty,vyattacfg,sudo,adm,dip,disk {user}'
             try:
                 cmd(command)
 
                 # we should not rely on the value stored in
                 # user_config['home_directory'], as a crazy user will choose
                 # username root or any other system user which will fail.
                 #
                 # XXX: Should we deny using root at all?
                 home_dir = getpwnam(user).pw_dir
                 render(f'{home_dir}/.ssh/authorized_keys', 'login/authorized_keys.j2',
                        user_config, permission=0o600,
                        formater=lambda _: _.replace("&quot;", '"'),
                        user=user, group='users')
 
             except Exception as e:
                 raise ConfigError(f'Adding user "{user}" raised exception: "{e}"')
 
             # Generate 2FA/MFA One-Time-Pad configuration
             if dict_search('authentication.otp.key', user_config):
                 render(f'{home_dir}/.google_authenticator', 'login/pam_otp_ga.conf.j2',
                        user_config, permission=0o400, user=user, group='users')
             else:
                 # delete configuration as it's not enabled for the user
                 if os.path.exists(f'{home_dir}/.google_authenticator'):
                     os.remove(f'{home_dir}/.google_authenticator')
 
     if 'rm_users' in login:
         for user in login['rm_users']:
             try:
                 # Disable user to prevent re-login
                 call(f'usermod -s /sbin/nologin {user}')
 
                 # Logout user if he is still logged in
                 if user in list(set([tmp[0] for tmp in users()])):
                     print(f'{user} is logged in, forcing logout!')
                     # re-run command until user is logged out
                     while run(f'pkill -HUP -u {user}'):
                         sleep(0.250)
 
                 # Remove user account but leave home directory in place. Re-run
                 # command until user is removed - userdel might return 8 as
                 # SSH sessions are not all yet properly cleaned away, thus we
                 # simply re-run the command until the account wen't away
                 while run(f'userdel {user}', stderr=DEVNULL):
                     sleep(0.250)
 
             except Exception as e:
                 raise ConfigError(f'Deleting user "{user}" raised exception: {e}')
 
-    # Enable RADIUS in PAM configuration
-    pam_cmd = '--remove'
+    # Enable/disable RADIUS in PAM configuration
+    cmd('pam-auth-update --disable radius-mandatory radius-optional')
     if 'radius' in login:
-        pam_cmd = '--enable'
-    cmd(f'pam-auth-update --package {pam_cmd} radius')
-
-    # Enable/Disable TACACS in PAM configuration
-    pam_cmd = '--remove'
+        if login['radius'].get('security_mode', '') == 'mandatory':
+            pam_profile = 'radius-mandatory'
+        else:
+            pam_profile = 'radius-optional'
+        cmd(f'pam-auth-update --enable {pam_profile}')
+
+    # Enable/disable TACACS+ in PAM configuration
+    cmd('pam-auth-update --disable tacplus-mandatory tacplus-optional')
     if 'tacacs' in login:
-        pam_cmd = '--enable'
-    cmd(f'pam-auth-update --package {pam_cmd} tacplus')
+        if login['tacacs'].get('security_mode', '') == 'mandatory':
+            pam_profile = 'tacplus-mandatory'
+        else:
+            pam_profile = 'tacplus-optional'
+        cmd(f'pam-auth-update --enable {pam_profile}')
 
     return None
 
 
 if __name__ == '__main__':
     try:
         c = get_config()
         verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
         print(e)
         exit(1)
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 3db06b368..3445da2cf 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -1,479 +1,479 @@
 #!/bin/bash
 # Copyright (C) 2021 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
 # published by the Free Software Foundation.
 #
 # 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, see <http://www.gnu.org/licenses/>.
 
 . /lib/lsb/init-functions
 
 : ${vyatta_env:=/etc/default/vyatta}
 source $vyatta_env
 
 declare progname=${0##*/}
 declare action=$1; shift
 
 declare -x BOOTFILE=$vyatta_sysconfdir/config/config.boot
 
 # If vyos-config= boot option is present, use that file instead
 for x in $(cat /proc/cmdline); do
     [[ $x = vyos-config=* ]] || continue
     VYOS_CONFIG="${x#vyos-config=}"
 done
 
 if [ ! -z "$VYOS_CONFIG" ]; then
     if [ -r "$VYOS_CONFIG" ]; then
         echo "Config selected manually: $VYOS_CONFIG"
         declare -x BOOTFILE="$VYOS_CONFIG"
     else
         echo "WARNING: Could not read selected config file, using default!"
     fi
 fi
 
 declare -a subinit
 declare -a all_subinits=( firewall )
 
 if [ $# -gt 0 ] ; then
     for s in $@ ; do
         [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
     done
 else
     for s in ${all_subinits[@]} ; do
         [ -x ${vyatta_sbindir}/${s}.init ] && subinit[${#subinit}]=$s
     done
 fi
 
 GROUP=vyattacfg
 
 # easy way to make empty file without any command
 empty()
 {
     >$1
 }
 
 # check if bootup of this portion is disabled
 disabled () {
     grep -q -w no-vyos-$1 /proc/cmdline
 }
 
 # if necessary, provide initial config
 init_bootfile () {
     if [ ! -r $BOOTFILE ] ; then
         if [ -f $vyatta_sysconfdir/config.boot.default ]; then
             cp $vyatta_sysconfdir/config.boot.default $BOOTFILE
         else
             $vyos_libexec_dir/system-versions-foot.py > $BOOTFILE
         fi
         chgrp ${GROUP} $BOOTFILE
         chmod 660 $BOOTFILE
     fi
 }
 
 # if necessary, migrate initial config
 migrate_bootfile ()
 {
     if [ -x $vyos_libexec_dir/run-config-migration.py ]; then
         log_progress_msg migrate
         sg ${GROUP} -c "$vyos_libexec_dir/run-config-migration.py $BOOTFILE"
     fi
 }
 
 # load the initial config
 load_bootfile ()
 {
     log_progress_msg configure
     (
         if [ -f /etc/default/vyatta-load-boot ]; then
             # build-specific environment for boot-time config loading
             source /etc/default/vyatta-load-boot
         fi
         if [ -x $vyos_libexec_dir/vyos-boot-config-loader.py ]; then
             sg ${GROUP} -c "$vyos_libexec_dir/vyos-boot-config-loader.py $BOOTFILE"
         fi
     )
 }
 
 # restore if missing pre-config script
 restore_if_missing_preconfig_script ()
 {
     if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script ]; then
         mkdir -p ${vyatta_sysconfdir}/config/scripts
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
         chmod 775 ${vyatta_sysconfdir}/config/scripts
         cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-preconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
         chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-preconfig-bootup.script
     fi
 }
 
 # execute the pre-config script
 run_preconfig_script ()
 {
     if [ -x $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyos-preconfig-bootup.script
     fi
 }
 
 # restore if missing post-config script
 restore_if_missing_postconfig_script ()
 {
     if [ ! -x ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script ]; then
         mkdir -p ${vyatta_sysconfdir}/config/scripts
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts
         chmod 775 ${vyatta_sysconfdir}/config/scripts
         cp ${vyos_rootfs_dir}/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script ${vyatta_sysconfdir}/config/scripts/
         chgrp ${GROUP} ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
         chmod 750 ${vyatta_sysconfdir}/config/scripts/vyos-postconfig-bootup.script
     fi
 }
 
 # execute the post-config scripts
 run_postconfig_scripts ()
 {
     if [ -x $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyatta-postconfig-bootup.script
     fi
     if [ -x $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script ]; then
         $vyatta_sysconfdir/config/scripts/vyos-postconfig-bootup.script
     fi
 }
 
 run_postupgrade_script ()
 {
     if [ -f $vyatta_sysconfdir/config/.upgraded ]; then
         # Run the system script
         /usr/libexec/vyos/system/post-upgrade
 
         # Run user scripts
         if [ -d $vyatta_sysconfdir/config/scripts/post-upgrade.d ]; then
             run-parts $vyatta_sysconfdir/config/scripts/post-upgrade.d
         fi
         rm -f $vyatta_sysconfdir/config/.upgraded
     fi
 }
 
 #
 # On image booted machines, we need to mount /boot from the image-specific
 # boot directory so that kernel package installation will put the
 # files in the right place.  We also have to mount /boot/grub from the
 # system-wide grub directory so that tools that edit the grub.cfg
 # file will find it in the expected location.
 #
 bind_mount_boot ()
 {
     persist_path=$(/opt/vyatta/sbin/vyos-persistpath)
     if [ $? == 0 ]; then
         if [ -e $persist_path/boot ]; then
             image_name=$(cat /proc/cmdline | sed -e s+^.*vyos-union=/boot/++ | sed -e 's/ .*$//')
 
             if [ -n "$image_name" ]; then
                 mount --bind $persist_path/boot/$image_name /boot
                 if [ $? -ne 0 ]; then
                     echo "Couldn't bind mount /boot"
                 fi
 
                 if [ ! -d /boot/grub ]; then
                     mkdir /boot/grub
                 fi
 
                 mount --bind $persist_path/boot/grub /boot/grub
                 if [ $? -ne 0 ]; then
                     echo "Couldn't bind mount /boot/grub"
                 fi
             fi
         fi
     fi
 }
 
 clear_or_override_config_files ()
 {
     for conf in snmp/snmpd.conf snmp/snmptrapd.conf snmp/snmp.conf \
         keepalived/keepalived.conf cron.d/vyos-crontab \
         ipvsadm.rules default/ipvsadm resolv.conf
     do
     if [ -s /etc/$conf ] ; then
         empty /etc/$conf
         chmod 0644 /etc/$conf
     fi
     done
 }
 
 update_interface_config ()
 {
     if [ -d /run/udev/vyos ]; then
         $vyos_libexec_dir/vyos-interface-rescan.py $BOOTFILE
     fi
 }
 
 cleanup_post_commit_hooks () {
     # Remove links from the post-commit hooks directory.
     # note that this approach only supports hooks that are "configured",
     # i.e., it does not support hooks that need to always be present.
     cpostdir=$(cli-shell-api getPostCommitHookDir)
     # exclude commits hooks from vyatta-cfg
     excluded="10vyatta-log-commit.pl 99vyos-user-postcommit-hooks"
     if [ -d "$cpostdir" ]; then
 	    for f in $cpostdir/*; do
 	        if [[ ! $excluded =~ $(basename $f) ]]; then
 		        rm -f $cpostdir/$(basename $f)
 	        fi
 	    done
     fi
 }
 
 # These are all the default security setting which are later
 # overridden when configuration is read. These are the values the
 # system defaults.
 security_reset ()
 {
 
     # restore NSS cofniguration back to sane system defaults
     # will be overwritten later when configuration is loaded
     cat <<EOF >/etc/nsswitch.conf
 passwd:         files
 group:          files
 shadow:         files
 gshadow:        files
 
 # Per T2678, commenting out myhostname
 hosts:          files dns #myhostname
 networks:       files
 
 protocols:      db files
 services:       db files
 ethers:         db files
 rpc:            db files
 
 netgroup:       nis
 EOF
 
     # restore PAM back to virgin state (no radius/tacacs services)
-    pam-auth-update --package --remove radius
+    pam-auth-update --disable radius-mandatory radius-optional
     rm -f /etc/pam_radius_auth.conf
-    pam-auth-update --package --remove tacplus
+    pam-auth-update --disable tacplus-mandatory tacplus-optional
     rm -f /etc/tacplus_nss.conf /etc/tacplus_servers
 
     # Certain configuration files are re-generated by the configuration
     # subsystem and must reside under /etc and can not easily be moved to /run.
     # So on every boot we simply delete any remaining files and let the CLI
     # regenearte them.
 
     # PPPoE
     rm -f /etc/ppp/peers/pppoe* /etc/ppp/peers/wlm*
 
     # IPSec
     rm -rf /etc/ipsec.conf /etc/ipsec.secrets
     find /etc/swanctl -type f | xargs rm -f
 
     # limit cleanup
     rm -f /etc/security/limits.d/10-vyos.conf
 
     # iproute2 cleanup
     rm -f /etc/iproute2/rt_tables.d/vyos-*.conf
 
     # Container
     rm -f /etc/containers/storage.conf /etc/containers/registries.conf /etc/containers/containers.conf
     # Clean all networks and re-create them from our CLI
     rm -f /etc/containers/networks/*
 
     # System Options (SSH/cURL)
     rm -f /etc/ssh/ssh_config.d/*vyos*.conf
     rm -f /etc/curlrc
 }
 
 # XXX: T3885 - generate persistend DHCPv6 DUID (Type4 - UUID based)
 gen_duid ()
 {
     DUID_FILE="/var/lib/dhcpv6/dhcp6c_duid"
     UUID_FILE="/sys/class/dmi/id/product_uuid"
     UUID_FILE_ALT="/sys/class/dmi/id/product_serial"
     if [ ! -f ${UUID_FILE} ] && [ ! -f ${UUID_FILE_ALT} ]; then
         return 1
     fi
 
     # DUID is based on the BIOS/EFI UUID. We omit additional - characters
     if [ -f ${UUID_FILE} ]; then
         UUID=$(cat ${UUID_FILE} | tr -d -)
     fi
     if [ -z ${UUID} ]; then
         UUID=$(uuidgen --sha1 --namespace @dns --name $(cat ${UUID_FILE_ALT}) | tr -d -)
     fi
     # Add DUID type4 (UUID) information
     DUID_TYPE="0004"
 
     # The length-information (as per RFC6355 UUID is 128 bits long) is in big-endian
     # format - beware when porting to ARM64. The length field consists out of the
     # UUID (128 bit + 16 bits DUID type) resulting in hex 12.
     DUID_LEN="0012"
     if [ "$(echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6 )" -eq 1 ]; then
         # true on little-endian (x86) systems
         DUID_LEN="1200"
     fi
 
     for i in $(echo -n ${DUID_LEN}${DUID_TYPE}${UUID} | sed 's/../& /g'); do
         echo -ne "\x$i"
     done > ${DUID_FILE}
 }
 
 start ()
 {
     # reset and clean config files
     security_reset || log_failure_msg "security reset failed"
 
     # some legacy directories migrated over from old rl-system.init
     mkdir -p /var/run/vyatta /var/log/vyatta
     chgrp vyattacfg /var/run/vyatta /var/log/vyatta
     chmod 775 /var/run/vyatta /var/log/vyatta
 
     log_daemon_msg "Waiting for NICs to settle down"
     # On boot time udev migth take a long time to reorder nic's, this will ensure that
     # all udev activity is completed and all nics presented at boot-time will have their
     # final name before continuing with vyos-router initialization.
     SECONDS=0
     udevadm settle
     STATUS=$?
     log_progress_msg "settled in ${SECONDS}sec."
     log_end_msg ${STATUS}
 
     # mountpoint for bpf maps required by xdp
     mount -t bpf none /sys/fs/bpf
 
     # Clear out Debian APT source config file
     empty /etc/apt/sources.list
 
     # Generate DHCPv6 DUID
     gen_duid || log_failure_msg "could not generate DUID"
 
     # Mount a temporary filesystem for container networks.
     # Configuration should be loaded from VyOS cli.
     cni_dir="/etc/cni/net.d"
     [ ! -d ${cni_dir} ] && mkdir -p ${cni_dir}
     mount -t tmpfs none ${cni_dir}
 
     # Init firewall
     nfct helper add rpc inet tcp
     nfct helper add rpc inet udp
     nfct helper add tns inet tcp
     nft -f /usr/share/vyos/vyos-firewall-init.conf || log_failure_msg "could not initiate firewall rules"
 
     # As VyOS does not execute commands that are not present in the CLI we call
     # the script by hand to have a single source for the login banner and MOTD
     ${vyos_conf_scripts_dir}/system_console.py || log_failure_msg "could not reset serial console"
     ${vyos_conf_scripts_dir}/system-login-banner.py || log_failure_msg "could not reset motd and issue files"
     ${vyos_conf_scripts_dir}/system-option.py || log_failure_msg "could not reset system option files"
     ${vyos_conf_scripts_dir}/system-ip.py || log_failure_msg "could not reset system IPv4 options"
     ${vyos_conf_scripts_dir}/system-ipv6.py || log_failure_msg "could not reset system IPv6 options"
     ${vyos_conf_scripts_dir}/conntrack.py || log_failure_msg "could not reset conntrack subsystem"
     ${vyos_conf_scripts_dir}/container.py || log_failure_msg "could not reset container subsystem"
 
     clear_or_override_config_files || log_failure_msg "could not reset config files"
 
     # enable some debugging before loading the configuration
     if grep -q vyos-debug /proc/cmdline; then
         log_action_begin_msg "Enable runtime debugging options"
         touch /tmp/vyos.container.debug
         touch /tmp/vyos.ifconfig.debug
         touch /tmp/vyos.frr.debug
         touch /tmp/vyos.container.debug
     fi
 
     log_action_begin_msg "Mounting VyOS Config"
     # ensure the vyatta_configdir supports a large number of inodes since
     # the config hierarchy is often inode-bound (instead of size).
     # impose a minimum and then scale up dynamically with the actual size
     # of the system memory.
     local tmem=$(sed -n 's/^MemTotal: \+\([0-9]\+\) kB$/\1/p' /proc/meminfo)
     local tpages
     local tmpfs_opts="nosuid,nodev,mode=775,nr_inodes=0" #automatically allocate inodes
     mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} \
       && chgrp ${GROUP} ${vyatta_configdir}
     log_action_end_msg $?
 
     # T5239: early read of system hostname as this value is read-only once during
     # FRR initialisation
     tmp=$(${vyos_libexec_dir}/read-saved-value.py --path "system host-name")
     hostnamectl set-hostname --static "$tmp"
 
     ${vyos_conf_scripts_dir}/system_frr.py || log_failure_msg "could not reset FRR config"
     # If for any reason FRR was not started by system_frr.py - start it anyways.
     # This is a safety net!
     systemctl start frr.service
 
     disabled bootfile || init_bootfile
 
     cleanup_post_commit_hooks
 
     log_daemon_msg "Starting VyOS router"
     disabled migrate || migrate_bootfile
 
     restore_if_missing_preconfig_script
 
     run_preconfig_script
 
     run_postupgrade_script
 
     update_interface_config
 
     for s in ${subinit[@]} ; do
     if ! disabled $s; then
         log_progress_msg $s
         if ! ${vyatta_sbindir}/${s}.init start
         then log_failure_msg
          exit 1
         fi
     fi
     done
 
     bind_mount_boot
 
     disabled configure || load_bootfile
     log_end_msg $?
 
     telinit q
     chmod g-w,o-w /
 
     restore_if_missing_postconfig_script
 
     run_postconfig_scripts
 }
 
 stop()
 {
     local -i status=0
     log_daemon_msg "Stopping VyOS router"
     for ((i=${#sub_inits[@]} - 1; i >= 0; i--)) ; do
     s=${subinit[$i]}
     log_progress_msg $s
     ${vyatta_sbindir}/${s}.init stop
     let status\|=$?
     done
     log_end_msg $status
     log_action_begin_msg "Un-mounting VyOS Config"
     umount ${vyatta_configdir}
     log_action_end_msg $?
 
     systemctl stop frr.service
 }
 
 case "$action" in
     start) start ;;
     stop)  stop ;;
     restart|force-reload) stop && start ;;
     *)  log_failure_msg "usage: $progname [ start|stop|restart ] [ subinit ... ]" ;
     false ;;
 esac
 
 exit $?
 
 # Local Variables:
 # mode: shell-script
 # sh-indentation: 4
 # End:
diff --git a/src/pam-configs/radius b/src/pam-configs/radius
deleted file mode 100644
index eee9cb93e..000000000
--- a/src/pam-configs/radius
+++ /dev/null
@@ -1,20 +0,0 @@
-Name: RADIUS authentication
-Default: no
-Priority: 257
-Auth-Type: Primary
-Auth:
-    [default=ignore success=2] pam_succeed_if.so service = sudo
-    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
-    [authinfo_unavail=ignore success=end default=ignore] pam_radius_auth.so
-
-Account-Type: Primary
-Account:
-    [default=ignore success=2] pam_succeed_if.so service = sudo
-    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
-    [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_radius_auth.so
-
-Session-Type: Additional
-Session:
-    [default=ignore success=2] pam_succeed_if.so service = sudo
-    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
-    [authinfo_unavail=ignore success=ok default=ignore] pam_radius_auth.so
diff --git a/src/pam-configs/radius-mandatory b/src/pam-configs/radius-mandatory
new file mode 100644
index 000000000..3368fe7ff
--- /dev/null
+++ b/src/pam-configs/radius-mandatory
@@ -0,0 +1,19 @@
+Name: RADIUS authentication (mandatory mode)
+Default: no
+Priority: 576
+
+Auth-Type: Primary
+Auth-Initial:
+    [default=ignore success=end auth_err=die perm_denied=die user_unknown=die] pam_radius_auth.so
+Auth:
+    [default=ignore success=end auth_err=die perm_denied=die user_unknown=die] pam_radius_auth.so use_first_pass
+
+Account-Type: Primary
+Account:
+    [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet
+    [default=ignore success=end] pam_radius_auth.so
+
+Session-Type: Additional
+Session:
+    [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet
+    [default=bad success=ok] pam_radius_auth.so
diff --git a/src/pam-configs/radius-optional b/src/pam-configs/radius-optional
new file mode 100644
index 000000000..73085061d
--- /dev/null
+++ b/src/pam-configs/radius-optional
@@ -0,0 +1,19 @@
+Name: RADIUS authentication (optional mode)
+Default: no
+Priority: 576
+
+Auth-Type: Primary
+Auth-Initial:
+    [default=ignore success=end] pam_radius_auth.so
+Auth:
+    [default=ignore success=end] pam_radius_auth.so use_first_pass
+
+Account-Type: Primary
+Account:
+    [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet
+    [default=ignore success=end] pam_radius_auth.so
+
+Session-Type: Additional
+Session:
+    [default=ignore success=1] pam_succeed_if.so user notingroup radius quiet
+    [default=ignore success=ok perm_denied=bad user_unknown=bad] pam_radius_auth.so
diff --git a/src/pam-configs/tacplus b/src/pam-configs/tacplus
deleted file mode 100644
index 66a1eaa4c..000000000
--- a/src/pam-configs/tacplus
+++ /dev/null
@@ -1,17 +0,0 @@
-Name: TACACS+ authentication
-Default: no
-Priority: 257
-Auth-Type: Primary
-Auth:
-    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
-    [authinfo_unavail=ignore success=end auth_err=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login
-
-Account-Type: Primary
-Account:
-    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
-    [authinfo_unavail=ignore success=end perm_denied=bad default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login
-
-Session-Type: Additional
-Session:
-    [default=ignore success=ignore] pam_succeed_if.so user ingroup aaa quiet
-    [authinfo_unavail=ignore success=ok default=ignore] pam_tacplus.so include=/etc/tacplus_servers login=login
diff --git a/src/pam-configs/tacplus-mandatory b/src/pam-configs/tacplus-mandatory
new file mode 100644
index 000000000..ffccece19
--- /dev/null
+++ b/src/pam-configs/tacplus-mandatory
@@ -0,0 +1,17 @@
+Name: TACACS+ authentication (mandatory mode)
+Default: no
+Priority: 576
+
+Auth-Type: Primary
+Auth:
+    [default=ignore success=end auth_err=die perm_denied=die user_unknown=die] pam_tacplus.so include=/etc/tacplus_servers login=login
+
+Account-Type: Primary
+Account:
+    [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet
+    [default=bad success=end] pam_tacplus.so include=/etc/tacplus_servers login=login
+
+Session-Type: Additional
+Session:
+    [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet
+    [default=bad success=ok] pam_tacplus.so include=/etc/tacplus_servers login=login
diff --git a/src/pam-configs/tacplus-optional b/src/pam-configs/tacplus-optional
new file mode 100644
index 000000000..095c3a164
--- /dev/null
+++ b/src/pam-configs/tacplus-optional
@@ -0,0 +1,17 @@
+Name: TACACS+ authentication (optional mode)
+Default: no
+Priority: 576
+
+Auth-Type: Primary
+Auth:
+    [default=ignore success=end] pam_tacplus.so include=/etc/tacplus_servers login=login
+
+Account-Type: Primary
+Account:
+    [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet
+    [default=ignore success=end auth_err=bad perm_denied=bad user_unknown=bad] pam_tacplus.so include=/etc/tacplus_servers login=login
+
+Session-Type: Additional
+Session:
+    [default=ignore success=1] pam_succeed_if.so user notingroup tacacs quiet
+    [default=ignore success=ok session_err=bad user_unknown=bad] pam_tacplus.so include=/etc/tacplus_servers login=login