Skip to content

Commit a2e5226

Browse files
committed
Fix --add-domain pipeline and 6 bugs from full script audit
- --add-domain: accept domain as CLI argument, fix stdin for curl pipe - --add-domain: prompt user for SSH creds to generate all 4 slipnet URLs - --add-domain: validate SSH creds for pipe chars to prevent URL corruption - --users: auto-generate slipnet SSH URLs after creating a user - --remove-tunnel: fix ${2:0:2} crash under set -u when $2 is unset - do_harden: wrap apply_service_hardening in if/else to prevent set -e crash - do_manage_users: add missing whitespace trim and pipe validation on del_user - help_press_enter: fix read crash on EOF under set -e - prompt_yn/prompt_input: read from /dev/tty for curl pipe compatibility
1 parent d62f7fe commit a2e5226

1 file changed

Lines changed: 125 additions & 28 deletions

File tree

dnstm-setup.sh

Lines changed: 125 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ prompt_yn() {
9999
while true; do
100100
echo ""
101101
echo -ne " ${BOLD}${question}${NC} ${yn_hint} ${DIM}[h=help]${NC} "
102-
read -r answer
102+
read -r answer </dev/tty 2>/dev/null || read -r answer
103103
answer=${answer:-$default}
104104
if [[ "$answer" =~ ^[Hh]$ ]]; then
105105
show_help_menu
@@ -123,7 +123,7 @@ prompt_input() {
123123
else
124124
echo -ne " ${BOLD}${question}${NC} ${DIM}(h=help)${NC}: " >&2
125125
fi
126-
read -r result
126+
read -r result </dev/tty 2>/dev/null || read -r result
127127
result=${result:-$default}
128128
if [[ "$result" =~ ^[Hh]$ ]]; then
129129
show_help_menu >&2
@@ -178,7 +178,7 @@ help_topic_header() {
178178
help_press_enter() {
179179
echo ""
180180
echo -ne " ${DIM}Press Enter to go back...${NC}"
181-
read -r
181+
read -r </dev/tty 2>/dev/null || read -r || true
182182
}
183183

184184
help_topic_domain() {
@@ -1220,7 +1220,11 @@ do_harden() {
12201220
fi
12211221

12221222
configure_systemd_resolved_no_stub || true
1223-
apply_service_hardening
1223+
if apply_service_hardening; then
1224+
print_ok "Runtime hardening applied"
1225+
else
1226+
print_warn "Runtime hardening reported issues; review systemctl status for dnstm units"
1227+
fi
12241228

12251229
echo ""
12261230
print_info "Current unit users:"
@@ -1802,19 +1806,66 @@ do_manage_users() {
18021806
continue
18031807
fi
18041808
echo ""
1809+
local user_created=false
18051810
if [[ -n "$new_pass" ]]; then
18061811
if timeout 30 sshtun-user create "$new_user" --insecure-password "$new_pass" 2>&1; then
18071812
print_ok "User '${new_user}' created"
1813+
user_created=true
18081814
else
18091815
print_fail "Failed to create user '${new_user}' (command timed out or failed)"
18101816
fi
18111817
else
18121818
if timeout 30 sshtun-user create "$new_user" </dev/null 2>&1; then
18131819
print_ok "User '${new_user}' created (random password assigned)"
1820+
user_created=true
18141821
else
18151822
print_fail "Failed to create user '${new_user}' (command timed out or failed)"
18161823
fi
18171824
fi
1825+
1826+
# Generate slipnet:// URLs for SSH tunnels
1827+
if [[ "$user_created" == true ]]; then
1828+
# Get the actual password (if auto-generated, read it back)
1829+
local final_pass="$new_pass"
1830+
if [[ -z "$final_pass" ]]; then
1831+
final_pass=$(sshtun-user show "$new_user" 2>/dev/null | grep -i pass | awk '{print $NF}' || true)
1832+
fi
1833+
if [[ -n "$final_pass" ]]; then
1834+
echo ""
1835+
print_info "SlipNet SSH config URLs for user '${new_user}':"
1836+
echo ""
1837+
# Find all SSH tunnels and generate URLs
1838+
local s_user="" s_pass=""
1839+
if detect_socks_auth; then
1840+
s_user="$SOCKS_USER"
1841+
s_pass="$SOCKS_PASS"
1842+
fi
1843+
local tunnel_domains
1844+
tunnel_domains=$(dnstm tunnel list 2>/dev/null || true)
1845+
# Get all unique base domains from tunnels
1846+
local domains
1847+
domains=$(echo "$tunnel_domains" | grep -o 'domain=[^ ]*' | sed 's/domain=//;s/^[a-z]*\.//' | sort -u || true)
1848+
for dom in $domains; do
1849+
DOMAIN="$dom"
1850+
local pubkey=""
1851+
# Find DNSTT pubkey for this domain
1852+
local dnstt_tag_name
1853+
dnstt_tag_name=$(echo "$tunnel_domains" | grep "domain=d\.${dom}" | grep -o 'tag=[^ ]*' | sed 's/tag=//' || true)
1854+
if [[ -n "$dnstt_tag_name" && -f "/etc/dnstm/tunnels/${dnstt_tag_name}/server.pub" ]]; then
1855+
pubkey=$(cat "/etc/dnstm/tunnels/${dnstt_tag_name}/server.pub" 2>/dev/null || true)
1856+
fi
1857+
# Slipstream + SSH
1858+
local url
1859+
url=$(generate_slipnet_url "slipstream_ssh" "s" "" "$new_user" "$final_pass" "$s_user" "$s_pass")
1860+
echo -e " ${GREEN}s.${dom}:${NC} ${url}"
1861+
# DNSTT + SSH
1862+
if [[ -n "$pubkey" ]]; then
1863+
url=$(generate_slipnet_url "dnstt_ssh" "ds" "$pubkey" "$new_user" "$final_pass" "$s_user" "$s_pass")
1864+
echo -e " ${GREEN}ds.${dom}:${NC} ${url}"
1865+
fi
1866+
done
1867+
fi
1868+
fi
18181869
;;
18191870
3)
18201871
echo ""
@@ -1850,10 +1901,15 @@ do_manage_users() {
18501901
echo ""
18511902
local del_user
18521903
del_user=$(prompt_input "Enter username to delete")
1904+
del_user=$(echo "$del_user" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
18531905
if [[ -z "$del_user" ]]; then
18541906
print_fail "Username cannot be empty"
18551907
continue
18561908
fi
1909+
if [[ "$del_user" == *"|"* ]]; then
1910+
print_fail "Username cannot contain the | character"
1911+
continue
1912+
fi
18571913
if prompt_yn "Are you sure you want to delete '${del_user}'?" "n"; then
18581914
if timeout 30 sshtun-user delete "$del_user" 2>&1; then
18591915
print_ok "User '${del_user}' deleted"
@@ -3170,24 +3226,39 @@ do_add_domain() {
31703226
local existing_domains
31713227
existing_domains=$(dnstm tunnel list 2>/dev/null | grep -o 'domain=[^ ]*' | sed 's/domain=//;s/^[a-z0-9]*\.//' | sort -u || true)
31723228

3173-
# Ask for new domain
3174-
while true; do
3175-
DOMAIN=$(prompt_input "Enter the new backup domain (e.g. backup.com)")
3229+
# Use domain from argument if provided, otherwise prompt
3230+
if [[ -n "$ADD_DOMAIN_ARG" ]]; then
3231+
DOMAIN="$ADD_DOMAIN_ARG"
31763232
DOMAIN=$(echo "$DOMAIN" | sed 's|^[[:space:]]*||;s|[[:space:]]*$||;s|^https\?://||;s|/.*$||')
3177-
if [[ -z "$DOMAIN" ]]; then
3178-
print_fail "Domain cannot be empty. Please try again."
3179-
elif [[ ! "$DOMAIN" =~ \. ]]; then
3180-
print_fail "Invalid domain (must contain a dot). Please try again."
3181-
elif [[ "$DOMAIN" =~ \.\. ]]; then
3182-
print_fail "Invalid domain (consecutive dots not allowed). Please try again."
3183-
elif [[ ! "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$ ]]; then
3184-
print_fail "Invalid domain (use only letters, numbers, dots, hyphens). Please try again."
3185-
elif echo "$existing_domains" | grep -qx "$DOMAIN"; then
3186-
print_fail "Domain '${DOMAIN}' is already in use by an existing tunnel. Please enter a different domain."
3187-
else
3188-
break
3233+
if [[ -z "$DOMAIN" ]] || [[ ! "$DOMAIN" =~ \. ]]; then
3234+
print_fail "Invalid domain: ${ADD_DOMAIN_ARG}"
3235+
exit 1
31893236
fi
3190-
done
3237+
if [[ -n "$existing_domains" ]] && echo "$existing_domains" | grep -qx "$DOMAIN"; then
3238+
print_fail "Domain '${DOMAIN}' is already in use by an existing tunnel."
3239+
exit 1
3240+
fi
3241+
else
3242+
# Interactive prompt — reopen /dev/tty in case stdin is a pipe
3243+
while true; do
3244+
echo -ne " ${BOLD}Enter the new backup domain (e.g. backup.com)${NC} ${DIM}(h=help)${NC}: " >&2
3245+
read -r DOMAIN </dev/tty || { print_fail "Cannot read input (stdin is a pipe). Pass domain as argument: --add-domain example.com"; exit 1; }
3246+
DOMAIN=$(echo "$DOMAIN" | sed 's|^[[:space:]]*||;s|[[:space:]]*$||;s|^https\?://||;s|/.*$||')
3247+
if [[ -z "$DOMAIN" ]]; then
3248+
print_fail "Domain cannot be empty. Please try again."
3249+
elif [[ ! "$DOMAIN" =~ \. ]]; then
3250+
print_fail "Invalid domain (must contain a dot). Please try again."
3251+
elif [[ "$DOMAIN" =~ \.\. ]]; then
3252+
print_fail "Invalid domain (consecutive dots not allowed). Please try again."
3253+
elif [[ ! "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$ ]]; then
3254+
print_fail "Invalid domain (use only letters, numbers, dots, hyphens). Please try again."
3255+
elif [[ -n "$existing_domains" ]] && echo "$existing_domains" | grep -qx "$DOMAIN"; then
3256+
print_fail "Domain '${DOMAIN}' is already in use by an existing tunnel. Please enter a different domain."
3257+
else
3258+
break
3259+
fi
3260+
done
3261+
fi
31913262

31923263
echo ""
31933264
print_ok "Domain: ${DOMAIN}"
@@ -3421,12 +3492,31 @@ do_add_domain() {
34213492
s_pass="$SOCKS_PASS"
34223493
fi
34233494
slipnet_url=$(generate_slipnet_url "ss" "t" "" "" "" "$s_user" "$s_pass")
3424-
echo -e " ${GREEN}${slip_tag}:${NC} ${slipnet_url}"
3495+
echo -e " ${GREEN}${slip_tag}:${NC} ${slipnet_url}"
34253496
if [[ -n "$DNSTT_PUBKEY" ]]; then
34263497
slipnet_url=$(generate_slipnet_url "dnstt" "d" "$DNSTT_PUBKEY" "" "" "$s_user" "$s_pass")
3427-
echo -e " ${GREEN}${dnstt_tag}:${NC} ${slipnet_url}"
3498+
echo -e " ${GREEN}${dnstt_tag}:${NC} ${slipnet_url}"
3499+
fi
3500+
3501+
# Ask user for SSH credentials to generate SSH tunnel URLs
3502+
echo ""
3503+
if prompt_yn "Generate SSH tunnel slipnet:// URLs?" "y"; then
3504+
local ssh_tun_user ssh_tun_pass
3505+
ssh_tun_user=$(prompt_input "SSH tunnel username")
3506+
ssh_tun_pass=$(prompt_input "SSH tunnel password")
3507+
if [[ "$ssh_tun_user" == *"|"* || "$ssh_tun_pass" == *"|"* ]]; then
3508+
print_fail "Username/password cannot contain the | character"
3509+
elif [[ -n "$ssh_tun_user" && -n "$ssh_tun_pass" ]]; then
3510+
slipnet_url=$(generate_slipnet_url "slipstream_ssh" "s" "" "$ssh_tun_user" "$ssh_tun_pass" "$s_user" "$s_pass")
3511+
echo -e " ${GREEN}${slip_ssh_tag}:${NC} ${slipnet_url}"
3512+
if [[ -n "$DNSTT_PUBKEY" ]]; then
3513+
slipnet_url=$(generate_slipnet_url "dnstt_ssh" "ds" "$DNSTT_PUBKEY" "$ssh_tun_user" "$ssh_tun_pass" "$s_user" "$s_pass")
3514+
echo -e " ${GREEN}${dnstt_ssh_tag}:${NC} ${slipnet_url}"
3515+
fi
3516+
else
3517+
echo -e " ${DIM}Skipped — username or password was empty.${NC}"
3518+
fi
34283519
fi
3429-
echo -e " ${DIM}SSH tunnel slipnet:// URLs require credentials. Use --manage → Manage SSH users first.${NC}"
34303520
echo ""
34313521

34323522
echo -e " ${DIM}To add more domains, run again: sudo bash $0 --add-domain${NC}"
@@ -3436,6 +3526,7 @@ do_add_domain() {
34363526
# ─── Parse Arguments ────────────────────────────────────────────────────────────
34373527

34383528
ADD_DOMAIN_MODE=false
3529+
ADD_DOMAIN_ARG=""
34393530
HARDEN_ONLY_MODE=false
34403531
MANAGE_USERS_MODE=false
34413532
DNSTT_MTU=1232
@@ -3464,7 +3555,7 @@ while [[ $# -gt 0 ]]; do
34643555
;;
34653556
--remove-tunnel)
34663557
# If $2 looks like another flag (starts with --), treat as no tag given
3467-
if [[ -n "${2:-}" && "${2:0:2}" != "--" ]]; then
3558+
if [[ -n "${2:-}" ]] && [[ "${2:0:2}" != "--" ]]; then
34683559
do_remove_tunnel "$2"
34693560
else
34703561
do_remove_tunnel ""
@@ -3477,7 +3568,13 @@ while [[ $# -gt 0 ]]; do
34773568
;;
34783569
--add-domain)
34793570
ADD_DOMAIN_MODE=true
3480-
shift
3571+
# Accept optional domain argument: --add-domain example.com
3572+
if [[ -n "${2:-}" ]] && [[ ! "$2" =~ ^-- ]]; then
3573+
ADD_DOMAIN_ARG="$2"
3574+
shift 2
3575+
else
3576+
shift
3577+
fi
34813578
;;
34823579
--users)
34833580
MANAGE_USERS_MODE=true
@@ -3507,9 +3604,9 @@ done
35073604
# ─── Validate conflicting flags ──────────────────────────────────────────────────
35083605

35093606
mode_count=0
3510-
[[ "$ADD_DOMAIN_MODE" == true ]] && ((mode_count++))
3511-
[[ "$HARDEN_ONLY_MODE" == true ]] && ((mode_count++))
3512-
[[ "$MANAGE_USERS_MODE" == true ]] && ((mode_count++))
3607+
[[ "$ADD_DOMAIN_MODE" == true ]] && ((mode_count++)) || true
3608+
[[ "$HARDEN_ONLY_MODE" == true ]] && ((mode_count++)) || true
3609+
[[ "$MANAGE_USERS_MODE" == true ]] && ((mode_count++)) || true
35133610
if [[ $mode_count -gt 1 ]]; then
35143611
echo "Error: --add-domain, --harden, and --users cannot be combined."
35153612
exit 1

0 commit comments

Comments
 (0)