Patch 0004

security: harden installer config, download, and

0004-security-harden-installer-config-download-and-extrac.patch

0001 0002 0003 0004 0005 0006 0007
From c9724dbba384299cda113ba3a3c4d6c97d116c65 Mon Sep 17 00:00:00 2001
From: "Jory A. Pratt" <geekypenguin@gmail.com>
Date: Thu, 2 Jul 2026 07:25:53 -0500
Subject: [PATCH 4/7] security: harden installer config, download, and
 extraction.

Install asl3-ini.sh, write config values with asl3_ini_set, quote cron
arguments, download sound files over HTTPS, validate ZIP entries before
extraction, and generate a true 350ms pause file.
---
 install_asl3_saytime_weather_timeformat.sh | 38 ++++++++++++++++++----
 1 file changed, 31 insertions(+), 7 deletions(-)

diff --git a/install_asl3_saytime_weather_timeformat.sh b/install_asl3_saytime_weather_timeformat.sh
index fdac579..973cba2 100755
--- a/install_asl3_saytime_weather_timeformat.sh
+++ b/install_asl3_saytime_weather_timeformat.sh
@@ -10,7 +10,7 @@ SBIN_DIR="/usr/local/sbin"
 SYSTEMD_DIR="/etc/systemd/system"
 CACHE_DIR="/var/cache/asl3-saytime-weather"
 SOUNDS_DIR="/usr/local/share/asterisk/sounds/custom"
-ORIGINAL_SOUND_ZIP_URL="http://198.58.124.150/tw/sound_files.zip"
+ORIGINAL_SOUND_ZIP_URL="https://198.58.124.150/tw/sound_files.zip"
 WORKDIR="/tmp/asl3-saytime-weather-timeformat.$$"
 
 LOCATION_ARG="${1:-}"
@@ -51,6 +51,7 @@ download_repo_files(){
   base="https://raw.githubusercontent.com/${REPO}/${BRANCH}"
 
   for f in \
+    bin/asl3-ini.sh \
     bin/asl3-weatherapi-update.sh \
     bin/show_weatherapi.sh \
     bin/saytime.pl \
@@ -64,8 +65,12 @@ download_repo_files(){
 }
 
 install_files(){
+  # shellcheck source=bin/asl3-ini.sh
+  source "$WORKDIR/bin/asl3-ini.sh"
+
   mkdir -p "$CONFIG_DIR" "$CACHE_DIR" "$SOUNDS_DIR"
 
+  install -m 0755 "$WORKDIR/bin/asl3-ini.sh" "$SBIN_DIR/asl3-ini.sh"
   install -m 0755 "$WORKDIR/bin/asl3-weatherapi-update.sh" "$SBIN_DIR/asl3-weatherapi-update.sh"
   install -m 0755 "$WORKDIR/bin/show_weatherapi.sh" "$SBIN_DIR/show_weatherapi.sh"
 
@@ -80,20 +85,36 @@ install_files(){
   fi
 
   if [ -n "$LOCATION_ARG" ]; then
-    sed -i "s/^LOCATION=.*/LOCATION=\"$LOCATION_ARG\"/" "$CONFIG_FILE"
+    asl3_ini_set "$CONFIG_FILE" LOCATION "$LOCATION_ARG"
   fi
 
   if [ -n "$NODE_ARG" ]; then
-    sed -i "s/^NODE=.*/NODE=\"$NODE_ARG\"/" "$CONFIG_FILE"
+    asl3_ini_set "$CONFIG_FILE" NODE "$NODE_ARG"
   fi
 
   if [ "$TIME_FORMAT_ARG" = "12" ] || [ "$TIME_FORMAT_ARG" = "24" ]; then
-    sed -i "s/^TIME_FORMAT=.*/TIME_FORMAT=\"$TIME_FORMAT_ARG\"/" "$CONFIG_FILE"
+    asl3_ini_set "$CONFIG_FILE" TIME_FORMAT "$TIME_FORMAT_ARG"
   fi
 
   chown -R asterisk:asterisk "$CACHE_DIR" 2>/dev/null || true
 }
 
+safe_unzip_sound_files(){
+  local zip="$1"
+  local dest="$2"
+  local relative_file
+
+  while IFS= read -r relative_file; do
+    [ -n "$relative_file" ] || continue
+    case "$relative_file" in
+      */) continue ;;
+    esac
+    asl3_zip_entry_is_safe "$relative_file" || die "Unsafe ZIP entry: $relative_file"
+  done < <(unzip -Z1 "$zip")
+
+  unzip -o "$zip" -d "$dest" >/dev/null || die "Could not extract recorded voice files to $dest"
+}
+
 install_recorded_sound_files(){
   mkdir -p "$SOUNDS_DIR"
 
@@ -109,12 +130,12 @@ install_recorded_sound_files(){
   zip="/tmp/sound_files.zip"
 
   curl -fL "$ORIGINAL_SOUND_ZIP_URL" -o "$zip" || die "Could not download recorded voice files."
-  unzip -o "$zip" -d "$SOUNDS_DIR" >/dev/null || die "Could not extract recorded voice files to $SOUNDS_DIR"
+  safe_unzip_sound_files "$zip" "$SOUNDS_DIR"
 
   chown -R asterisk:asterisk "$SOUNDS_DIR" 2>/dev/null || true
   chmod -R a+rX "$SOUNDS_DIR"
 
-  sox -n -r 8000 -c 1 "$SOUNDS_DIR/pause-350ms.gsm" trim 0.0 1.0
+  sox -n -r 8000 -c 1 "$SOUNDS_DIR/pause-350ms.gsm" trim 0.0 0.35
   chmod 644 "$SOUNDS_DIR/pause-350ms.gsm"
   chown asterisk:asterisk "$SOUNDS_DIR/pause-350ms.gsm" 2>/dev/null || true
 }
@@ -132,6 +153,9 @@ cron_has_block(){
 }
 
 install_cron_template(){
+  # shellcheck source=bin/asl3-ini.sh
+  source "$WORKDIR/bin/asl3-ini.sh"
+
   if cron_has_block; then
     log "Crontab block already exists; leaving it unchanged."
     return 0
@@ -140,7 +164,7 @@ install_cron_template(){
   local active_line comment_line
 
   if [ -n "$LOCATION_ARG" ] && [ -n "$NODE_ARG" ]; then
-    active_line="0 * * * * /usr/bin/nice -19 /usr/bin/perl /usr/local/sbin/saytime.pl $LOCATION_ARG $NODE_ARG >/dev/null 2>&1"
+    active_line="0 * * * * /usr/bin/nice -19 /usr/bin/perl /usr/local/sbin/saytime.pl $(asl3_quote_cron_arg "$LOCATION_ARG") $(asl3_quote_cron_arg "$NODE_ARG") >/dev/null 2>&1"
     comment_line="# Active hourly announcement installed by setup script."
   else
     active_line="# 0 * * * * /usr/bin/nice -19 /usr/bin/perl /usr/local/sbin/saytime.pl YOUR_ZIP_CITY_OR_AIRPORT YOUR_NODE_NUMBER >/dev/null 2>&1"
-- 
2.47.3