8a7337
#!/bin/bash
8a7337
# This script configures running chronyd to use NTP servers obtained from
8a7337
# DHCP and _ntp._udp DNS SRV records. Files with servers from DHCP are managed
8a7337
# externally (e.g. by a dhclient script). Files with servers from DNS SRV
8a7337
# records are updated here using the dig utility. The script can also list
8a7337
# and set static sources in the chronyd configuration file.
8a7337
8a7337
chronyc=/usr/bin/chronyc
8a7337
chrony_conf=/etc/chrony.conf
8a7337
chrony_service=chronyd.service
b94f58
helper_dir=/run/chrony-helper
8a7337
added_servers_file=$helper_dir/added_servers
8a7337
8a7337
network_sysconfig_file=/etc/sysconfig/network
b94f58
nm_servers_files="$helper_dir/nm-dhcp.*"
8a7337
dhclient_servers_files="/var/lib/dhclient/chrony.servers.*"
8a7337
dnssrv_servers_files="$helper_dir/dnssrv@*"
8a7337
dnssrv_timer_prefix=chrony-dnssrv@
8a7337
8a7337
. $network_sysconfig_file &> /dev/null
8a7337
8a7337
chrony_command() {
9f7b83
    $chronyc -n -m "$@"
8a7337
}
8a7337
8a7337
is_running() {
8a7337
    chrony_command "tracking" &> /dev/null
8a7337
}
8a7337
8a7337
get_servers_files() {
b94f58
    [ "$PEERNTP" != "no" ] && echo "$nm_servers_files"
8a7337
    [ "$PEERNTP" != "no" ] && echo "$dhclient_servers_files"
8a7337
    echo "$dnssrv_servers_files"
8a7337
}
8a7337
8a7337
is_update_needed() {
8a7337
    for file in $(get_servers_files) $added_servers_file; do
8a7337
        [ -e "$file" ] && return 0
8a7337
    done
8a7337
    return 1
8a7337
}
8a7337
b94f58
remove_daemon_state() {
b94f58
    rm -f $added_servers_file
b94f58
}
b94f58
8a7337
update_daemon() {
8a7337
    local all_servers_with_args all_servers added_servers
8a7337
8a7337
    if ! is_running; then
b94f58
        remove_daemon_state
8a7337
        return 0
8a7337
    fi
8a7337
8a7337
    all_servers_with_args=$(cat $(get_servers_files) 2> /dev/null)
8a7337
8a7337
    all_servers=$(
8a7337
        echo "$all_servers_with_args" |
8a7337
            while read -r server serverargs; do
8a7337
                echo "$server"
8a7337
            done | sort -u)
8a7337
    added_servers=$( (
8a7337
        cat $added_servers_file 2> /dev/null
8a7337
        echo "$all_servers_with_args" |
8a7337
            while read -r server serverargs; do
8a7337
                [ -z "$server" ] && continue
8a7337
                chrony_command "add server $server $serverargs" &> /dev/null &&
8a7337
                    echo "$server"
8a7337
            done) | sort -u)
8a7337
8a7337
    comm -23 <(echo -n "$added_servers") <(echo -n "$all_servers") |
8a7337
        while read -r server; do
9f7b83
            chrony_command -c sources -a 2>/dev/null |
9f7b83
                    while IFS=, read -r type _ address _; do
9f7b83
                [ "$type" = "^" ] || continue
9f7b83
                [ "$(chrony_command "sourcename $address")" = "$server" ] || continue
9f7b83
                chrony_command "delete $address" &> /dev/null
9f7b83
                break
9f7b83
            done
8a7337
        done
8a7337
8a7337
    added_servers=$(comm -12 <(echo -n "$added_servers") <(echo -n "$all_servers"))
8a7337
8a7337
    if [ -n "$added_servers" ]; then
8a7337
        echo "$added_servers" > $added_servers_file
8a7337
    else
8a7337
        rm -f $added_servers_file
8a7337
    fi
8a7337
}
8a7337
8a7337
get_dnssrv_servers() {
8a7337
    local name=$1 output
8a7337
8a7337
    if ! command -v dig &> /dev/null; then
8a7337
        echo "Missing dig (DNS lookup utility)" >&2
8a7337
        return 1
8a7337
    fi
8a7337
8a7337
    output=$(dig "$name" srv +short +ndots=2 +search 2> /dev/null) || return 0
8a7337
8a7337
    echo "$output" | while read -r _ _ port target; do
8a7337
        server=${target%.}
8a7337
        [ -z "$server" ] && continue
8a7337
        echo "$server port $port ${NTPSERVERARGS:-iburst}"
8a7337
    done
8a7337
}
8a7337
8a7337
check_dnssrv_name() {
8a7337
    local name=$1
8a7337
8a7337
    if [ -z "$name" ]; then
8a7337
        echo "No DNS SRV name specified" >&2
8a7337
        return 1
8a7337
    fi
8a7337
8a7337
    if [ "${name:0:9}" != _ntp._udp ]; then
8a7337
        echo "DNS SRV name $name doesn't start with _ntp._udp" >&2
8a7337
        return 1
8a7337
    fi
8a7337
}
8a7337
8a7337
update_dnssrv_servers() {
8a7337
    local name=$1
8a7337
    local srv_file=$helper_dir/dnssrv@$name servers
8a7337
8a7337
    check_dnssrv_name "$name" || return 1
8a7337
8a7337
    servers=$(get_dnssrv_servers "$name")
8a7337
    if [ -n "$servers" ]; then
8a7337
        echo "$servers" > "$srv_file"
8a7337
    else
8a7337
        rm -f "$srv_file"
8a7337
    fi
8a7337
}
8a7337
8a7337
set_dnssrv_timer() {
8a7337
    local state=$1 name=$2
8a7337
    local srv_file=$helper_dir/dnssrv@$name servers
8a7337
    local timer
8a7337
8a7337
    timer=$dnssrv_timer_prefix$(systemd-escape "$name").timer || return 1
8a7337
8a7337
    check_dnssrv_name "$name" || return 1
8a7337
8a7337
    if [ "$state" = enable ]; then
8a7337
        systemctl enable "$timer"
8a7337
        systemctl start "$timer"
8a7337
    elif [ "$state" = disable ]; then
8a7337
        systemctl stop "$timer"
8a7337
        systemctl disable "$timer"
8a7337
        rm -f "$srv_file"
8a7337
    fi
8a7337
}
8a7337
8a7337
list_dnssrv_timers() {
8a7337
    systemctl --all --full -t timer list-units | grep "^$dnssrv_timer_prefix" | \
8a7337
            sed "s|^$dnssrv_timer_prefix\(.*\)\.timer.*|\1|" |
8a7337
        while read -r name; do
8a7337
            systemd-escape --unescape "$name"
8a7337
        done
8a7337
}
8a7337
8a7337
prepare_helper_dir() {
8a7337
    mkdir -p $helper_dir
8a7337
    exec 100> $helper_dir/lock
8a7337
    if ! flock -w 20 100; then
8a7337
        echo "Failed to lock $helper_dir" >&2
8a7337
        return 1
8a7337
    fi
8a7337
}
8a7337
8a7337
is_source_line() {
8a7337
    local pattern="^[ \t]*(server|pool|peer|refclock)[ \t]+[^ \t]+"
8a7337
    [[ "$1" =~ $pattern ]]
8a7337
}
8a7337
8a7337
list_static_sources() {
8a7337
    while read -r line; do
8a7337
        if is_source_line "$line"; then
8a7337
            echo "$line"
8a7337
        fi
8a7337
    done < $chrony_conf
8a7337
}
8a7337
8a7337
set_static_sources() {
8a7337
    local new_config tmp_conf
8a7337
8a7337
    new_config=$(
8a7337
        sources=$(
8a7337
            while read -r line; do
8a7337
                is_source_line "$line" && echo "$line"
8a7337
            done)
8a7337
8a7337
        while read -r line; do
8a7337
            if ! is_source_line "$line"; then
8a7337
                echo "$line"
8a7337
                continue
8a7337
            fi
8a7337
8a7337
            tmp_sources=$(
8a7337
                local removed=0
8a7337
8a7337
                echo "$sources" | while read -r line2; do
8a7337
                    if [ "$removed" -ne 0 ] || [ "$line" != "$line2" ]; then
8a7337
                        echo "$line2"
8a7337
                    else
8a7337
                        removed=1
8a7337
                    fi
8a7337
                done)
8a7337
8a7337
            [ "$sources" == "$tmp_sources" ] && continue
8a7337
            sources=$tmp_sources
8a7337
            echo "$line"
8a7337
        done < $chrony_conf
8a7337
8a7337
        echo "$sources"
8a7337
    )
8a7337
8a7337
    tmp_conf=${chrony_conf}.tmp
8a7337
8a7337
    cp -a $chrony_conf $tmp_conf &&
8a7337
        echo "$new_config" > $tmp_conf &&
8a7337
        mv $tmp_conf $chrony_conf || return 1
8a7337
8a7337
    systemctl try-restart $chrony_service
8a7337
}
8a7337
8a7337
print_help() {
8a7337
    echo "Usage: $0 COMMAND"
8a7337
    echo
8a7337
    echo "Commands:"
b94f58
    echo "	create-helper-directory"
8a7337
    echo "	update-daemon"
b94f58
    echo "	remove-daemon-state"
8a7337
    echo "	update-dnssrv-servers NAME"
8a7337
    echo "	enable-dnssrv NAME"
8a7337
    echo "	disable-dnssrv NAME"
8a7337
    echo "	list-dnssrv"
8a7337
    echo "	list-static-sources"
8a7337
    echo "	set-static-sources < sources.list"
8a7337
    echo "	is-running"
8a7337
    echo "	command CHRONYC-COMMAND"
8a7337
}
8a7337
8a7337
case "$1" in
b94f58
    create-helper-directory)
b94f58
        prepare_helper_dir
b94f58
        ;;
8a7337
    update-daemon|add-dhclient-servers|remove-dhclient-servers)
8a7337
        is_update_needed || exit 0
8a7337
        prepare_helper_dir && update_daemon
8a7337
        ;;
b94f58
    remove-daemon-state)
b94f58
        remove_daemon_state
b94f58
        ;;
8a7337
    update-dnssrv-servers)
8a7337
        prepare_helper_dir && update_dnssrv_servers "$2" && update_daemon
8a7337
        ;;
8a7337
    enable-dnssrv)
8a7337
        set_dnssrv_timer enable "$2"
8a7337
        ;;
8a7337
    disable-dnssrv)
8a7337
        set_dnssrv_timer disable "$2" && prepare_helper_dir && update_daemon
8a7337
        ;;
8a7337
    list-dnssrv)
8a7337
        list_dnssrv_timers
8a7337
        ;;
8a7337
    list-static-sources)
8a7337
        list_static_sources
8a7337
        ;;
8a7337
    set-static-sources)
8a7337
        set_static_sources
8a7337
        ;;
8a7337
    is-running)
8a7337
        is_running
8a7337
        ;;
8a7337
    command|forced-command)
8a7337
        chrony_command "$2"
8a7337
        ;;
8a7337
    *)
8a7337
        print_help
8a7337
        exit 2
8a7337
esac
8a7337
8a7337
exit $?