summaryrefslogtreecommitdiff
path: root/src/system
diff options
context:
space:
mode:
Diffstat (limited to 'src/system')
-rw-r--r--src/system/grub_update.py108
-rwxr-xr-xsrc/system/keepalived-fifo.py2
-rwxr-xr-xsrc/system/on-dhcp-event.sh105
-rwxr-xr-xsrc/system/on-dhcpv6-event.sh87
-rwxr-xr-xsrc/system/standalone_root_pw_reset178
-rwxr-xr-xsrc/system/uacctd_stop.py68
6 files changed, 517 insertions, 31 deletions
diff --git a/src/system/grub_update.py b/src/system/grub_update.py
new file mode 100644
index 000000000..5a7d8eb72
--- /dev/null
+++ b/src/system/grub_update.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+#
+# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This file is part of VyOS.
+#
+# VyOS 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 3 of the License, or (at your option) any later
+# version.
+#
+# VyOS 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
+# VyOS. If not, see <https://www.gnu.org/licenses/>.
+
+from pathlib import Path
+from sys import exit
+
+from vyos.system import disk, grub, image, compat, SYSTEM_CFG_VER
+from vyos.template import render
+
+
+def cfg_check_update() -> bool:
+ """Check if GRUB structure update is required
+
+ Returns:
+ bool: False if not required, True if required
+ """
+ current_ver = grub.get_cfg_ver()
+ if current_ver and current_ver >= SYSTEM_CFG_VER:
+ return False
+
+ return True
+
+
+if __name__ == '__main__':
+ if image.is_live_boot():
+ exit(0)
+
+ if image.is_running_as_container():
+ exit(0)
+
+ # Skip everything if update is not required
+ if not cfg_check_update():
+ exit(0)
+
+ # find root directory of persistent storage
+ root_dir = disk.find_persistence()
+
+ # read current GRUB config
+ grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}'
+ vars = grub.vars_read(grub_cfg_main)
+ modules = grub.modules_read(grub_cfg_main)
+ vyos_menuentries = compat.parse_menuentries(grub_cfg_main)
+ vyos_versions = compat.find_versions(vyos_menuentries)
+ unparsed_items = compat.filter_unparsed(grub_cfg_main)
+ # compatibilty for raid installs
+ search_root = compat.get_search_root(unparsed_items)
+ common_dict = {}
+ common_dict['search_root'] = search_root
+ # find default values
+ default_entry = vyos_menuentries[int(vars['default'])]
+ default_settings = {
+ 'default': grub.gen_version_uuid(default_entry['version']),
+ 'bootmode': default_entry['bootmode'],
+ 'console_type': default_entry['console_type'],
+ 'console_num': default_entry['console_num'],
+ 'console_speed': default_entry['console_speed']
+ }
+ vars.update(default_settings)
+
+ # create new files
+ grub_cfg_vars = f'{root_dir}/{grub.CFG_VYOS_VARS}'
+ grub_cfg_modules = f'{root_dir}/{grub.CFG_VYOS_MODULES}'
+ grub_cfg_platform = f'{root_dir}/{grub.CFG_VYOS_PLATFORM}'
+ grub_cfg_menu = f'{root_dir}/{grub.CFG_VYOS_MENU}'
+ grub_cfg_options = f'{root_dir}/{grub.CFG_VYOS_OPTIONS}'
+
+ Path(image.GRUB_DIR_VYOS).mkdir(exist_ok=True)
+ grub.vars_write(grub_cfg_vars, vars)
+ grub.modules_write(grub_cfg_modules, modules)
+ grub.common_write(grub_common=common_dict)
+ render(grub_cfg_menu, grub.TMPL_GRUB_MENU, {})
+ render(grub_cfg_options, grub.TMPL_GRUB_OPTS, {})
+
+ # create menu entries
+ for vyos_ver in vyos_versions:
+ boot_opts = None
+ for entry in vyos_menuentries:
+ if entry.get('version') == vyos_ver and entry.get(
+ 'bootmode') == 'normal':
+ boot_opts = entry.get('boot_opts')
+ grub.version_add(vyos_ver, root_dir, boot_opts)
+
+ # update structure version
+ cfg_ver = compat.update_cfg_ver(root_dir)
+ grub.write_cfg_ver(cfg_ver, root_dir)
+
+ if compat.mode():
+ compat.render_grub_cfg(root_dir)
+ else:
+ render(grub_cfg_main, grub.TMPL_GRUB_MAIN, {})
+
+ exit(0)
diff --git a/src/system/keepalived-fifo.py b/src/system/keepalived-fifo.py
index 5e19bdbad..6d33e372d 100755
--- a/src/system/keepalived-fifo.py
+++ b/src/system/keepalived-fifo.py
@@ -41,7 +41,7 @@ logger.addHandler(logs_handler_syslog)
logger.setLevel(logging.DEBUG)
mdns_running_file = '/run/mdns_vrrp_active'
-mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns-repeater.py'
+mdns_update_command = 'sudo /usr/libexec/vyos/conf_mode/service_mdns_repeater.py'
# class for all operations
class KeepalivedFifo:
diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh
index 49e53d7e1..47c276270 100755
--- a/src/system/on-dhcp-event.sh
+++ b/src/system/on-dhcp-event.sh
@@ -1,53 +1,98 @@
#!/bin/bash
+#
+# Copyright (C) 2024 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/>.
+#
+#
-# This script came from ubnt.com forum user "bradd" in the following post
-# http://community.ubnt.com/t5/EdgeMAX/Automatic-DNS-resolution-of-DHCP-client-names/td-p/651311
-# It has been modified by Ubiquiti to update the /etc/host file
-# instead of adding to the CLI.
-# Thanks to forum user "itsmarcos" for bug fix & improvements
-# Thanks to forum user "ruudboon" for multiple domain fix
-# Thanks to forum user "chibby85" for expire patch and static-mapping
-
-if [ $# -lt 5 ]; then
+if [ $# -lt 1 ]; then
echo Invalid args
logger -s -t on-dhcp-event "Invalid args \"$@\""
exit 1
fi
action=$1
-client_name=$2
-client_ip=$3
-client_mac=$4
-domain=$5
hostsd_client="/usr/bin/vyos-hostsd-client"
+get_subnet_domain_name () {
+ python3 <<EOF
+from vyos.kea import kea_get_active_config
+from vyos.utils.dict import dict_search_args
+
+config = kea_get_active_config('4')
+shared_networks = dict_search_args(config, 'arguments', f'Dhcp4', 'shared-networks')
+
+found = False
+
+if shared_networks:
+ for network in shared_networks:
+ for subnet in network[f'subnet4']:
+ if subnet['id'] == $1:
+ for option in subnet['option-data']:
+ if option['name'] == 'domain-name':
+ print(option['data'])
+ found = True
+
+ if not found:
+ for option in network['option-data']:
+ if option['name'] == 'domain-name':
+ print(option['data'])
+EOF
+}
+
case "$action" in
- commit) # add mapping for new lease
- if [ -z "$client_name" ]; then
- logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead"
- client_name=$(echo "client-"$client_mac | tr : -)
- fi
-
- if [ "$domain" == "..YYZ!" ]; then
- client_fqdn_name=$client_name
- client_search_expr=$client_name
- else
- client_fqdn_name=$client_name.$domain
- client_search_expr="$client_name\\.$domain"
- fi
- $hostsd_client --add-hosts "$client_fqdn_name,$client_ip" --tag "dhcp-server-$client_ip" --apply
+ lease4_renew|lease4_recover)
exit 0
;;
- release) # delete mapping for released address
+ lease4_release|lease4_expire|lease4_decline) # delete mapping for released/declined address
+ client_ip=$LEASE4_ADDRESS
$hostsd_client --delete-hosts --tag "dhcp-server-$client_ip" --apply
exit 0
;;
+ leases4_committed) # process committed leases (added/renewed/recovered)
+ for ((i = 0; i < $LEASES4_SIZE; i++)); do
+ client_ip_var="LEASES4_AT${i}_ADDRESS"
+ client_mac_var="LEASES4_AT${i}_HWADDR"
+ client_name_var="LEASES4_AT${i}_HOSTNAME"
+ client_subnet_id_var="LEASES4_AT${i}_SUBNET_ID"
+
+ client_ip=${!client_ip_var}
+ client_mac=${!client_mac_var}
+ client_name=${!client_name_var%.}
+ client_subnet_id=${!client_subnet_id_var}
+
+ if [ -z "$client_name" ]; then
+ logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead"
+ client_name=$(echo "host-$client_mac" | tr : -)
+ fi
+
+ client_domain=$(get_subnet_domain_name $client_subnet_id)
+
+ if [[ -n "$client_domain" ]] && ! [[ $client_name =~ .*$client_domain$ ]]; then
+ client_name="$client_name.$client_domain"
+ fi
+
+ $hostsd_client --add-hosts "$client_name,$client_ip" --tag "dhcp-server-$client_ip" --apply
+ done
+
+ exit 0
+ ;;
+
*)
logger -s -t on-dhcp-event "Invalid command \"$1\""
exit 1
;;
esac
-
-exit 0
diff --git a/src/system/on-dhcpv6-event.sh b/src/system/on-dhcpv6-event.sh
new file mode 100755
index 000000000..cbb370999
--- /dev/null
+++ b/src/system/on-dhcpv6-event.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+#
+# Copyright (C) 2024 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/>.
+#
+#
+
+if [ $# -lt 1 ]; then
+ echo Invalid args
+ logger -s -t on-dhcpv6-event "Invalid args \"$@\""
+ exit 1
+fi
+
+action=$1
+
+case "$action" in
+ lease6_renew|lease6_recover)
+ exit 0
+ ;;
+
+ lease6_release|lease6_expire|lease6_decline)
+ ifname=$QUERY6_IFACE_NAME
+ lease_addr=$LEASE6_ADDRESS
+ lease_prefix_len=$LEASE6_PREFIX_LEN
+
+ if [[ "$LEASE6_TYPE" != "IA_PD" ]]; then
+ exit 0
+ fi
+
+ logger -s -t on-dhcpv6-event "Processing route deletion for ${lease_addr}/${lease_prefix_len}"
+ route_cmd="sudo -n /sbin/ip -6 route del ${lease_addr}/${lease_prefix_len}"
+
+ # the ifname is not always present, like in LEASE6_VALID_LIFETIME=0 updates,
+ # but 'route del' works either way. Use interface only if there is one.
+ if [[ "$ifname" != "" ]]; then
+ route_cmd+=" dev ${ifname}"
+ fi
+ route_cmd+=" proto static"
+ eval "$route_cmd"
+
+ exit 0
+ ;;
+
+ leases6_committed)
+ for ((i = 0; i < $LEASES6_SIZE; i++)); do
+ ifname=$QUERY6_IFACE_NAME
+ requester_link_local=$QUERY6_REMOTE_ADDR
+ lease_type_var="LEASES6_AT${i}_TYPE"
+ lease_ip_var="LEASES6_AT${i}_ADDRESS"
+ lease_prefix_len_var="LEASES6_AT${i}_PREFIX_LEN"
+
+ lease_type=${!lease_type_var}
+
+ if [[ "$lease_type" != "IA_PD" ]]; then
+ continue
+ fi
+
+ lease_ip=${!lease_ip_var}
+ lease_prefix_len=${!lease_prefix_len_var}
+
+ logger -s -t on-dhcpv6-event "Processing PD route for ${lease_addr}/${lease_prefix_len}. Link local: ${requester_link_local} ifname: ${ifname}"
+
+ sudo -n /sbin/ip -6 route replace ${lease_ip}/${lease_prefix_len} \
+ via ${requester_link_local} \
+ dev ${ifname} \
+ proto static
+ done
+
+ exit 0
+ ;;
+
+ *)
+ logger -s -t on-dhcpv6-event "Invalid command \"$1\""
+ exit 1
+ ;;
+esac
diff --git a/src/system/standalone_root_pw_reset b/src/system/standalone_root_pw_reset
new file mode 100755
index 000000000..c82cea321
--- /dev/null
+++ b/src/system/standalone_root_pw_reset
@@ -0,0 +1,178 @@
+#!/bin/bash
+# **** License ****
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 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.
+#
+# This code was originally developed by Vyatta, Inc.
+# Portions created by Vyatta are Copyright (C) 2007 Vyatta, Inc.
+# All Rights Reserved.
+#
+# Author: Bob Gilligan <gilligan@vyatta.com>
+# Description: Standalone script to set the admin passwd to new value
+# value. Note: This script can ONLY be run as a standalone
+# init program by grub.
+#
+# **** End License ****
+
+# The Vyatta config file:
+CF=/opt/vyatta/etc/config/config.boot
+
+# Admin user name
+ADMIN=vyos
+
+set_encrypted_password() {
+ sed -i \
+ -e "/ user $1 {/,/encrypted-password/s/encrypted-password .*\$/encrypted-password \"$2\"/" $3
+}
+
+
+# How long to wait for user to respond, in seconds
+TIME_TO_WAIT=30
+
+change_password() {
+ local user=$1
+ local pwd1="1"
+ local pwd2="2"
+
+ until [ "$pwd1" == "$pwd2" ]
+ do
+ read -p "Enter $user password: " -r -s pwd1
+ echo
+ read -p "Retype $user password: " -r -s pwd2
+ echo
+
+ if [ "$pwd1" != "$pwd2" ]
+ then echo "Passwords do not match"
+ fi
+ done
+
+ # set the password for the user then store it in the config
+ # so the user is recreated on the next full system boot.
+ local epwd=$(mkpasswd --method=sha-512 "$pwd1")
+ # escape any slashes in resulting password
+ local eepwd=$(sed 's:/:\\/:g' <<< $epwd)
+ set_encrypted_password $user $eepwd $CF
+}
+
+# System is so messed up that doing anything would be a mistake
+dead() {
+ echo $*
+ echo
+ echo "This tool can only recover missing admininistrator password."
+ echo "It is not a full system restore"
+ echo
+ echo -n "Hit return to reboot system: "
+ read
+ /sbin/reboot -f
+}
+
+echo "Standalone root password recovery tool."
+echo
+#
+# Check to see if we are running in standalone mode. We'll
+# know that we are if our pid is 1.
+#
+if [ "$$" != "1" ]; then
+ echo "This tool can only be run in standalone mode."
+ exit 1
+fi
+
+#
+# OK, now we know we are running in standalone mode. Talk to the
+# user.
+#
+echo -n "Do you wish to reset the admin password? (y or n) "
+read -t $TIME_TO_WAIT response
+if [ "$?" != "0" ]; then
+ echo
+ echo "Response not received in time."
+ echo "The admin password will not be reset."
+ echo "Rebooting in 5 seconds..."
+ sleep 5
+ echo
+ /sbin/reboot -f
+fi
+
+response=${response:0:1}
+if [ "$response" != "y" -a "$response" != "Y" ]; then
+ echo "OK, the admin password will not be reset."
+ echo -n "Rebooting in 5 seconds..."
+ sleep 5
+ echo
+ /sbin/reboot -f
+fi
+
+echo -en "Which admin account do you want to reset? [$ADMIN] "
+read admin_user
+ADMIN=${admin_user:-$ADMIN}
+
+echo "Starting process to reset the admin password..."
+
+echo "Re-mounting root filesystem read/write..."
+mount -o remount,rw /
+
+if [ ! -f /etc/passwd ]
+then dead "Missing password file"
+fi
+
+if [ ! -d /opt/vyatta/etc/config ]
+then dead "Missing VyOS config directory /opt/vyatta/etc/config"
+fi
+
+# Leftover from V3.0
+if grep -q /opt/vyatta/etc/config /etc/fstab
+then
+ echo "Mounting the config filesystem..."
+ mount /opt/vyatta/etc/config/
+fi
+
+if [ ! -f $CF ]
+then dead "$CF file not found"
+fi
+
+if ! grep -q 'system {' $CF
+then dead "$CF file does not contain system settings"
+fi
+
+if ! grep -q ' login {' $CF
+then
+ # Recreate login section of system
+ sed -i -e '/system {/a\
+ login {\
+ }' $CF
+fi
+
+if ! grep -q " user $ADMIN " $CF
+then
+ echo "Recreating administrator $ADMIN in $CF..."
+ sed -i -e "/ login {/a\\
+ user $ADMIN {\\
+ authentication {\\
+ encrypted-password \$6$IhbXHdwgYkLnt/$VRIsIN5c2f2v4L2l4F9WPDrRDEtWXzH75yBswmWGERAdX7oBxmq6m.sWON6pO6mi6mrVgYBxdVrFcCP5bI.nt.\\
+ plaintext-password \"\"\\
+ }\\
+ level admin\\
+ }" $CF
+fi
+
+echo "Saving backup copy of config.boot..."
+cp $CF ${CF}.before_pwrecovery
+sync
+
+echo "Setting the administrator ($ADMIN) password..."
+change_password $ADMIN
+
+echo $(date "+%b%e %T") $(hostname) "Admin password changed" \
+ | tee -a /var/log/auth.log >>/var/log/messages
+
+sync
+
+echo "System will reboot in 10 seconds..."
+sleep 10
+/sbin/reboot -f
diff --git a/src/system/uacctd_stop.py b/src/system/uacctd_stop.py
new file mode 100755
index 000000000..a1b57335b
--- /dev/null
+++ b/src/system/uacctd_stop.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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/>.
+
+# Control pmacct daemons in a tricky way.
+# Pmacct has signal processing in a main loop, together with packet
+# processing. Because of this, while it is waiting for packets, it cannot
+# handle the control signal. We need to start the systemctl command and then
+# send some packets to pmacct to wake it up
+
+from argparse import ArgumentParser
+from socket import socket, AF_INET, SOCK_DGRAM
+from sys import exit
+from time import sleep
+
+from psutil import Process
+
+
+def stop_process(pid: int, timeout: int) -> None:
+ """Send a signal to uacctd
+ and then send packets to special address predefined in a firewall
+ to unlock main loop in uacctd and finish the process properly
+
+ Args:
+ pid (int): uacctd PID
+ timeout (int): seconds to wait for a process end
+ """
+ # find a process
+ uacctd = Process(pid)
+ uacctd.terminate()
+
+ # create a socket
+ trigger = socket(AF_INET, SOCK_DGRAM)
+
+ first_cycle: bool = True
+ while uacctd.is_running() and timeout:
+ print('sending a packet to uacctd...')
+ trigger.sendto(b'WAKEUP', ('127.0.254.0', 1))
+ # do not sleep during first attempt
+ if not first_cycle:
+ sleep(1)
+ timeout -= 1
+ first_cycle = False
+
+
+if __name__ == '__main__':
+ parser = ArgumentParser()
+ parser.add_argument('process_id',
+ type=int,
+ help='PID file of uacctd core process')
+ parser.add_argument('timeout',
+ type=int,
+ help='time to wait for process end')
+ args = parser.parse_args()
+ stop_process(args.process_id, args.timeout)
+ exit()