V1.0.1 terminalphone.sh

This commit is contained in:
Here ForAwhile 2026-02-18 19:50:13 +00:00
parent 40b5f81c51
commit 2dbb895cd7

View file

@ -9,7 +9,7 @@ set -euo pipefail
# CONFIGURATION # CONFIGURATION
#============================================================================= #=============================================================================
APP_NAME="TerminalPhone" APP_NAME="TerminalPhone"
VERSION="1.0.0" VERSION="1.0.1"
BASE_DIR="$(dirname "$(readlink -f "$0")")" BASE_DIR="$(dirname "$(readlink -f "$0")")"
DATA_DIR="$BASE_DIR/.terminalphone" DATA_DIR="$BASE_DIR/.terminalphone"
TOR_DIR="$DATA_DIR/tor_data" TOR_DIR="$DATA_DIR/tor_data"
@ -23,6 +23,7 @@ PTT_FLAG="$DATA_DIR/run/ptt_$$"
CONNECTED_FLAG="$DATA_DIR/run/connected_$$" CONNECTED_FLAG="$DATA_DIR/run/connected_$$"
RECV_PIPE="$DATA_DIR/run/recv_$$" RECV_PIPE="$DATA_DIR/run/recv_$$"
SEND_PIPE="$DATA_DIR/run/send_$$" SEND_PIPE="$DATA_DIR/run/send_$$"
CIPHER_RUNTIME_FILE="$DATA_DIR/run/cipher_$$"
# Defaults # Defaults
LISTEN_PORT=7777 LISTEN_PORT=7777
@ -32,6 +33,7 @@ OPUS_FRAMESIZE=60 # ms
SAMPLE_RATE=8000 # Hz SAMPLE_RATE=8000 # Hz
PTT_KEY=" " # spacebar PTT_KEY=" " # spacebar
CHUNK_DURATION=1 # seconds per audio chunk CHUNK_DURATION=1 # seconds per audio chunk
CIPHER="aes-256-cbc" # OpenSSL cipher for encryption
# ANSI Colors # ANSI Colors
RED='\033[0;31m' RED='\033[0;31m'
@ -152,7 +154,9 @@ save_config() {
LISTEN_PORT=$LISTEN_PORT LISTEN_PORT=$LISTEN_PORT
TOR_SOCKS_PORT=$TOR_SOCKS_PORT TOR_SOCKS_PORT=$TOR_SOCKS_PORT
OPUS_BITRATE=$OPUS_BITRATE OPUS_BITRATE=$OPUS_BITRATE
OPUS_FRAMESIZE=$OPUS_FRAMESIZE
PTT_KEY="$PTT_KEY" PTT_KEY="$PTT_KEY"
CIPHER="$CIPHER"
EOF EOF
} }
@ -422,25 +426,33 @@ set_shared_secret() {
# Encrypt stdin to stdout # Encrypt stdin to stdout
encrypt_stream() { encrypt_stream() {
openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" -nosalt 2>/dev/null local c="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE")
openssl enc -"${c}" -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" -nosalt 2>/dev/null
} }
# Decrypt stdin to stdout # Decrypt stdin to stdout
decrypt_stream() { decrypt_stream() {
openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" -nosalt 2>/dev/null local c="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE")
openssl enc -d -"${c}" -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" -nosalt 2>/dev/null
} }
# Encrypt a file # Encrypt a file
encrypt_file() { encrypt_file() {
local infile="$1" outfile="$2" local infile="$1" outfile="$2"
openssl enc -aes-256-cbc -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" \ local c="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE")
openssl enc -"${c}" -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" \
-in "$infile" -out "$outfile" 2>/dev/null -in "$infile" -out "$outfile" 2>/dev/null
} }
# Decrypt a file # Decrypt a file
decrypt_file() { decrypt_file() {
local infile="$1" outfile="$2" local infile="$1" outfile="$2"
openssl enc -d -aes-256-cbc -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" \ local c="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && c=$(cat "$CIPHER_RUNTIME_FILE")
openssl enc -d -"${c}" -pbkdf2 -iter 10000 -pass "pass:${SHARED_SECRET}" \
-in "$infile" -out "$outfile" 2>/dev/null -in "$infile" -out "$outfile" 2>/dev/null
} }
@ -722,6 +734,61 @@ transmit_loop() {
done done
} }
#=============================================================================
# CALL CLEANUP — RESET EVERYTHING TO FRESH STATE
#=============================================================================
cleanup_call() {
# Restore terminal to sane state
if [ -n "$ORIGINAL_STTY" ]; then
stty "$ORIGINAL_STTY" 2>/dev/null || true
fi
stty sane 2>/dev/null || true
ORIGINAL_STTY=""
# Close pipe file descriptors to unblock any blocking reads
exec 3<&- 2>/dev/null || true
exec 4>&- 2>/dev/null || true
# Kill all call-related processes by PID files
for pidfile in "$PID_DIR"/socat.pid "$PID_DIR"/socat_call.pid "$PID_DIR"/recv_loop.pid; do
if [ -f "$pidfile" ]; then
local pid
pid=$(cat "$pidfile" 2>/dev/null)
if [ -n "$pid" ]; then
kill "$pid" 2>/dev/null || true
kill -9 "$pid" 2>/dev/null || true
fi
rm -f "$pidfile"
fi
done
# Kill recording process if active
if [ -n "$REC_PID" ]; then
kill "$REC_PID" 2>/dev/null || true
kill -9 "$REC_PID" 2>/dev/null || true
REC_PID=""
fi
# Wait briefly for processes to die
sleep 0.2
# Remove all runtime files for this PID
rm -f "$PTT_FLAG" "$CONNECTED_FLAG"
rm -f "$RECV_PIPE" "$SEND_PIPE"
rm -f "$CIPHER_RUNTIME_FILE"
rm -f "$DATA_DIR/run/remote_id_$$"
rm -f "$DATA_DIR/run/remote_cipher_$$"
rm -f "$DATA_DIR/run/incoming_$$"
# Clean temp audio files
rm -f "$AUDIO_DIR"/*.opus "$AUDIO_DIR"/*.bin "$AUDIO_DIR"/*.txt "$AUDIO_DIR"/*.wav 2>/dev/null || true
# Reset state variables
CALL_ACTIVE=0
REC_PID=""
}
# Start listening for incoming calls # Start listening for incoming calls
listen_for_call() { listen_for_call() {
if [ -z "$SHARED_SECRET" ]; then if [ -z "$SHARED_SECRET" ]; then
@ -772,9 +839,8 @@ listen_for_call() {
log_ok "Call connected!" log_ok "Call connected!"
in_call_session "$RECV_PIPE" "$SEND_PIPE" "" in_call_session "$RECV_PIPE" "$SEND_PIPE" ""
# Cleanup after call ends # Full cleanup after call ends
kill "$socat_pid" 2>/dev/null || true cleanup_call
rm -f "$CONNECTED_FLAG" "$RECV_PIPE" "$SEND_PIPE" "$incoming_flag"
} }
# Call a remote .onion address # Call a remote .onion address
@ -825,13 +891,50 @@ call_remote() {
log_err "Failed to connect. Check the address and ensure Tor is running." log_err "Failed to connect. Check the address and ensure Tor is running."
fi fi
rm -f "$CONNECTED_FLAG" "$RECV_PIPE" "$SEND_PIPE" # Full cleanup after call ends
cleanup_call
} }
#============================================================================= #=============================================================================
# IN-CALL SESSION — PTT VOICE LOOP # IN-CALL SESSION — PTT VOICE LOOP
#============================================================================= #=============================================================================
# Draw the call header (reusable for redraw after settings)
draw_call_header() {
local _remote="${1:-}"
local _rcipher="${2:-}"
clear >&2
if [ -n "$_remote" ]; then
echo -e "\n${BOLD}${BG_GREEN}${WHITE} CALL CONNECTED ${NC} ${CYAN}${_remote}${NC}\n" >&2
else
echo -e "\n${BOLD}${BG_GREEN}${WHITE} CALL CONNECTED ${NC}\n" >&2
fi
# Show cipher info — always show both local and remote
local cipher_upper="${CIPHER^^}"
if [ -n "$_rcipher" ]; then
local rcipher_upper="${_rcipher^^}"
if [ "$_rcipher" = "$CIPHER" ]; then
echo -e " ${GREEN}${NC} Local cipher: ${WHITE}${cipher_upper}${NC}" >&2
echo -e " ${GREEN}${NC} Remote cipher: ${WHITE}${rcipher_upper}${NC}" >&2
else
echo -e " ${RED}${NC} Local cipher: ${WHITE}${cipher_upper}${NC}" >&2
echo -e " ${RED}${NC} Remote cipher: ${WHITE}${rcipher_upper}${NC}" >&2
fi
else
echo -e " ${GREEN}${NC} Local cipher: ${WHITE}${cipher_upper}${NC}" >&2
echo -e " ${DIM}${NC} Remote cipher: ${DIM}waiting...${NC}" >&2
fi
echo "" >&2
echo -e " ${BOLD}Controls:${NC}" >&2
echo -e " ${GREEN}[SPACE]${NC} -- Push-to-Talk" >&2
echo -e " ${CYAN}[T]${NC} -- Send text message" >&2
echo -e " ${YELLOW}[S]${NC} -- Settings" >&2
echo -e " ${RED}[Q]${NC} -- Hang up" >&2
echo -e "" >&2
}
in_call_session() { in_call_session() {
local recv_pipe="$1" local recv_pipe="$1"
local send_pipe="$2" local send_pipe="$2"
@ -841,45 +944,59 @@ in_call_session() {
rm -f "$PTT_FLAG" rm -f "$PTT_FLAG"
mkdir -p "$AUDIO_DIR" mkdir -p "$AUDIO_DIR"
# Write cipher to runtime file so subshells can track changes
echo "$CIPHER" > "$CIPHER_RUNTIME_FILE"
# Open persistent file descriptors for the pipes # Open persistent file descriptors for the pipes
exec 3< "$recv_pipe" # fd 3 = read from remote exec 3< "$recv_pipe" # fd 3 = read from remote
exec 4> "$send_pipe" # fd 4 = write to remote exec 4> "$send_pipe" # fd 4 = write to remote
# Send our onion address for caller ID # Send our onion address and cipher for handshake
local my_onion local my_onion
my_onion=$(get_onion) my_onion=$(get_onion)
if [ -n "$my_onion" ]; then if [ -n "$my_onion" ]; then
echo "ID:${my_onion}" >&4 2>/dev/null || true echo "ID:${my_onion}" >&4 2>/dev/null || true
fi fi
echo "CIPHER:${CIPHER}" >&4 2>/dev/null || true
# Remote address (populated by receive loop) # Remote address and cipher (populated by handshake / receive loop)
local remote_id_file="$DATA_DIR/run/remote_id_$$" local remote_id_file="$DATA_DIR/run/remote_id_$$"
rm -f "$remote_id_file" local remote_cipher_file="$DATA_DIR/run/remote_cipher_$$"
rm -f "$remote_id_file" "$remote_cipher_file"
# If we don't know the remote address yet (listener), wait briefly for handshake # If we don't know the remote address yet (listener), wait briefly for handshake
local remote_display="$known_remote" local remote_display="$known_remote"
local remote_cipher=""
if [ -z "$remote_display" ]; then if [ -z "$remote_display" ]; then
# Read the first line — should be the ID handshake # Read first line — should be ID
local first_line="" local first_line=""
if read -r -t 3 first_line <&3 2>/dev/null; then if read -r -t 3 first_line <&3 2>/dev/null; then
if [[ "$first_line" == ID:* ]]; then if [[ "$first_line" == ID:* ]]; then
remote_display="${first_line#ID:}" remote_display="${first_line#ID:}"
echo "$remote_display" > "$remote_id_file" echo "$remote_display" > "$remote_id_file"
elif [[ "$first_line" == CIPHER:* ]]; then
remote_cipher="${first_line#CIPHER:}"
fi fi
fi fi
fi fi
# Show header with remote address # Try to read CIPHER: line (quick, non-blocking)
if [ -n "$remote_display" ]; then if [ -z "$remote_cipher" ]; then
echo -e "\n${BOLD}${BG_GREEN}${WHITE} CALL CONNECTED ${NC} ${CYAN}${remote_display}${NC}\n" local cline=""
else if read -r -t 1 cline <&3 2>/dev/null; then
echo -e "\n${BOLD}${BG_GREEN}${WHITE} CALL CONNECTED ${NC}\n" if [[ "$cline" == CIPHER:* ]]; then
remote_cipher="${cline#CIPHER:}"
fi fi
echo -e " ${BOLD}Controls:${NC}" fi
echo -e " ${GREEN}[SPACE]${NC} -- Push-to-Talk" fi
echo -e " ${CYAN}[T]${NC} -- Send text message"
echo -e " ${RED}[Q]${NC} -- Hang up" # Save remote cipher for later redraws
echo -e "" if [ -n "$remote_cipher" ]; then
echo "$remote_cipher" > "$remote_cipher_file"
fi
# Draw call header
draw_call_header "$remote_display" "$remote_cipher"
# Start receive handler in background # Start receive handler in background
# Protocol: ID:<onion>, PTT_START, PTT_STOP, PING, # Protocol: ID:<onion>, PTT_START, PTT_STOP, PING,
@ -903,6 +1020,29 @@ in_call_session() {
local remote_addr="${line#ID:}" local remote_addr="${line#ID:}"
echo "$remote_addr" > "$remote_id_file" echo "$remote_addr" > "$remote_id_file"
;; ;;
CIPHER:*)
# Remote side sent/changed their cipher — save and update display
local rc="${line#CIPHER:}"
echo "$rc" > "$remote_cipher_file" 2>/dev/null || true
# Read current local cipher from runtime file
local _cur_cipher="$CIPHER"
[ -f "$CIPHER_RUNTIME_FILE" ] && _cur_cipher=$(cat "$CIPHER_RUNTIME_FILE")
local _cu="${_cur_cipher^^}"
local _ru="${rc^^}"
# Update cipher lines in-place using ANSI cursor positioning (rows 4-5)
local _dot_color
if [ "$rc" = "$_cur_cipher" ]; then
_dot_color="$GREEN"
else
_dot_color="$RED"
fi
printf '\033[s' >&2
printf '\033[4;1H\033[K' >&2
printf ' %b●%b Local cipher: %b%s%b\r\n' "$_dot_color" "$NC" "$WHITE" "$_cu" "$NC" >&2
printf '\033[K' >&2
printf ' %b●%b Remote cipher: %b%s%b' "$_dot_color" "$NC" "$WHITE" "$_ru" "$NC" >&2
printf '\033[u' >&2
;;
MSG:*) MSG:*)
# Encrypted text message received # Encrypted text message received
local msg_b64="${line#MSG:}" local msg_b64="${line#MSG:}"
@ -961,9 +1101,9 @@ in_call_session() {
local ptt_active=0 local ptt_active=0
if [ $IS_TERMUX -eq 1 ]; then if [ $IS_TERMUX -eq 1 ]; then
echo -ne "\r ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Talk [T]=Chat [Q]=Hang up${NC} " >&2 echo -ne "\r ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
else else
echo -ne "\r ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [Q]=Hang up${NC} " >&2 echo -ne "\r ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
fi fi
while [ -f "$CONNECTED_FLAG" ]; do while [ -f "$CONNECTED_FLAG" ]; do
@ -981,7 +1121,7 @@ in_call_session() {
ptt_active=0 ptt_active=0
stop_and_send stop_and_send
echo "PTT_STOP" >&4 2>/dev/null || true echo "PTT_STOP" >&4 2>/dev/null || true
echo -ne "\r ${GREEN}${BOLD} Sent! ${NC} ${DIM}[SPACE]=Talk [T]=Chat [Q]=Hang up${NC} " >&2 echo -ne "\r ${GREEN}${BOLD} Sent! ${NC} ${DIM}[SPACE]=Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
fi fi
else else
# LINUX: Hold-to-talk # LINUX: Hold-to-talk
@ -1015,7 +1155,7 @@ in_call_session() {
ptt_active=0 ptt_active=0
stop_and_send stop_and_send
echo "PTT_STOP" >&4 2>/dev/null || true echo "PTT_STOP" >&4 2>/dev/null || true
echo -ne "\r ${GREEN}${BOLD} Sent! ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [Q]=Hang up${NC} " >&2 echo -ne "\r ${GREEN}${BOLD} Sent! ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
fi fi
elif [ "$key" = "t" ] || [ "$key" = "T" ]; then elif [ "$key" = "t" ] || [ "$key" = "T" ]; then
@ -1045,33 +1185,32 @@ in_call_session() {
stty raw -echo -icanon min 0 time 1 stty raw -echo -icanon min 0 time 1
echo "" >&2 echo "" >&2
if [ $IS_TERMUX -eq 1 ]; then if [ $IS_TERMUX -eq 1 ]; then
echo -ne " ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Talk [T]=Chat [Q]=Hang up${NC} " >&2 echo -ne " ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
else else
echo -ne " ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [Q]=Hang up${NC} " >&2 echo -ne " ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
fi
elif [ "$key" = "s" ] || [ "$key" = "S" ]; then
# Mid-call settings
stty "$ORIGINAL_STTY" 2>/dev/null || stty sane
# Flush any leftover raw mode input
read -r -t 0.1 -n 10000 2>/dev/null || true
settings_menu
# Redraw call header and switch back to raw mode
local _rd="" _rc=""
[ -f "$remote_id_file" ] && _rd=$(cat "$remote_id_file" 2>/dev/null)
[ -z "$_rd" ] && _rd="$known_remote"
[ -f "$remote_cipher_file" ] && _rc=$(cat "$remote_cipher_file" 2>/dev/null)
draw_call_header "$_rd" "$_rc"
stty raw -echo -icanon min 0 time 1
if [ $IS_TERMUX -eq 1 ]; then
echo -ne " ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
else
echo -ne " ${GREEN}${BOLD} Ready ${NC} ${DIM}[SPACE]=Hold to Talk [T]=Chat [S]=Settings [Q]=Hang up${NC} " >&2
fi fi
fi fi
done done
# Restore terminal
stty "$ORIGINAL_STTY" 2>/dev/null || stty sane
ORIGINAL_STTY=""
# Close pipe fds FIRST to unblock any blocking reads
rm -f "$PTT_FLAG" "$CONNECTED_FLAG"
exec 3<&- 2>/dev/null || true
exec 4>&- 2>/dev/null || true
# Now kill background processes (they'll exit since fds are closed)
kill "$recv_pid" 2>/dev/null || true
if [ -n "$REC_PID" ]; then
kill "$REC_PID" 2>/dev/null || true
fi
# Brief wait, don't hang forever
sleep 0.5
wait "$recv_pid" 2>/dev/null || true
CALL_ACTIVE=0
rm -f "$remote_id_file"
echo -e "\n${BOLD}${RED} CALL ENDED ${NC}\n" echo -e "\n${BOLD}${RED} CALL ENDED ${NC}\n"
} }
@ -1204,11 +1343,206 @@ show_status() {
# Config # Config
echo -e "\n ${DIM}Listen port: $LISTEN_PORT${NC}" echo -e "\n ${DIM}Listen port: $LISTEN_PORT${NC}"
echo -e " ${DIM}SOCKS port: $TOR_SOCKS_PORT${NC}" echo -e " ${DIM}SOCKS port: $TOR_SOCKS_PORT${NC}"
echo -e " ${DIM}Cipher: $CIPHER${NC}"
echo -e " ${DIM}Opus bitrate: ${OPUS_BITRATE}kbps${NC}" echo -e " ${DIM}Opus bitrate: ${OPUS_BITRATE}kbps${NC}"
echo -e " ${DIM}Opus frame: ${OPUS_FRAMESIZE}ms${NC}"
echo -e " ${DIM}PTT key: [SPACEBAR]${NC}" echo -e " ${DIM}PTT key: [SPACEBAR]${NC}"
echo "" echo ""
} }
#=============================================================================
# SETTINGS MENU
#=============================================================================
settings_menu() {
while true; do
clear
echo -e "\n${BOLD}${CYAN}═══ Settings ═══${NC}\n"
echo -e " ${DIM}Current cipher: ${NC}${WHITE}${CIPHER}${NC}"
echo -e " ${DIM}Current Opus bitrate: ${NC}${WHITE}${OPUS_BITRATE} kbps${NC}"
echo -e " ${DIM}Current Opus frame: ${NC}${WHITE}${OPUS_FRAMESIZE} ms${NC}\n"
echo -e " ${BOLD}${WHITE}1${NC} ${CYAN}${NC} Change encryption cipher"
echo -e " ${BOLD}${WHITE}2${NC} ${CYAN}${NC} Change Opus encoding quality"
echo -e " ${BOLD}${WHITE}0${NC} ${CYAN}${NC} ${DIM}Back to main menu${NC}"
echo ""
echo -ne " ${BOLD}Select: ${NC}"
read -r schoice
case "$schoice" in
1) settings_cipher ;;
2) settings_opus ;;
0|q|Q) return ;;
*)
echo -e "\n ${RED}Invalid choice${NC}"
sleep 1
;;
esac
done
}
settings_cipher() {
echo -e "\n${BOLD}${CYAN}═══ Select Encryption Cipher ═══${NC}\n"
echo -e " ${DIM}Current: ${NC}${GREEN}${CIPHER}${NC}\n"
# Curated cipher list ranked from strongest to adequate
# Only includes ciphers verified to work with openssl enc -pbkdf2
# Excludes: ECB modes (pattern leakage), DES/RC2/RC4/Blowfish (weak), aliases
local ciphers=(
# ── 256-bit (Strongest) ──
"aes-256-ctr"
"aes-256-cbc"
"aes-256-cfb"
"aes-256-ofb"
"chacha20"
"camellia-256-ctr"
"camellia-256-cbc"
"aria-256-ctr"
"aria-256-cbc"
# ── 192-bit (Strong) ──
"aes-192-ctr"
"aes-192-cbc"
"camellia-192-ctr"
"camellia-192-cbc"
"aria-192-ctr"
"aria-192-cbc"
# ── 128-bit (Adequate) ──
"aes-128-ctr"
"aes-128-cbc"
"camellia-128-ctr"
"camellia-128-cbc"
"aria-128-ctr"
"aria-128-cbc"
)
local total=${#ciphers[@]}
while true; do
clear
echo -e "\n${BOLD}${CYAN}═══ Available Ciphers ═══${NC}"
echo -e " ${DIM}Current: ${NC}${GREEN}${CIPHER}${NC}"
echo -e " ${DIM}${total} ciphers, ranked strongest → adequate${NC}\n"
local tier=""
for ((i = 0; i < total; i++)); do
local num=$((i + 1))
local c="${ciphers[$i]}"
# Print tier headers
if [ $i -eq 0 ]; then
echo -e " ${GREEN}${BOLD}── 256-bit (Strongest) ──${NC}"
elif [ $i -eq 9 ]; then
echo -e " ${YELLOW}${BOLD}── 192-bit (Strong) ──${NC}"
elif [ $i -eq 15 ]; then
echo -e " ${WHITE}${BOLD}── 128-bit (Adequate) ──${NC}"
fi
if [ "$c" = "$CIPHER" ]; then
printf " ${GREEN}${BOLD}%4d${NC} ${CYAN}${NC} ${GREEN}%-30s ◄ current${NC}\n" "$num" "$c"
else
printf " ${WHITE}${BOLD}%4d${NC} ${CYAN}${NC} %-30s\n" "$num" "$c"
fi
done
echo ""
echo -e " ${DIM}[0] cancel${NC}"
echo -ne " ${BOLD}Enter cipher number: ${NC}"
read -r cinput
case "$cinput" in
0|q|Q)
return
;;
'')
;;
*)
if [[ "$cinput" =~ ^[0-9]+$ ]] && [ "$cinput" -ge 1 ] && [ "$cinput" -le "$total" ]; then
local selected="${ciphers[$((cinput - 1))]}"
# Validate that openssl can actually use this cipher
if echo "test" | openssl enc -"${selected}" -pbkdf2 -pass pass:test -nosalt 2>/dev/null | openssl enc -d -"${selected}" -pbkdf2 -pass pass:test -nosalt &>/dev/null; then
CIPHER="$selected"
save_config
# Update runtime file for live mid-call sync
[ -f "$CIPHER_RUNTIME_FILE" ] && echo "$CIPHER" > "$CIPHER_RUNTIME_FILE"
# Notify remote side if in a call
if [ "$CALL_ACTIVE" -eq 1 ]; then
echo "CIPHER:${CIPHER}" >&4 2>/dev/null || true
fi
echo -e "\n ${GREEN}${BOLD}${NC} Cipher set to ${WHITE}${BOLD}${CIPHER}${NC}"
else
echo -e "\n ${RED}${BOLD}${NC} Cipher '${selected}' failed validation — not compatible with stream encryption"
fi
echo -ne " ${DIM}Press Enter to continue...${NC}"
read -r
return
else
echo -e "\n ${RED}Invalid number${NC}"
sleep 1
fi
;;
esac
done
}
settings_opus() {
echo -e "\n${BOLD}${CYAN}═══ Opus Encoding Quality ═══${NC}\n"
echo -e " ${DIM}Current bitrate: ${NC}${GREEN}${OPUS_BITRATE} kbps${NC}\n"
local -a presets=(6 8 12 16 24 32 48 64)
local -a labels=(
"6 kbps — Minimum (very low bandwidth)"
"8 kbps — Low (narrowband voice)"
"12 kbps — Medium-Low (clear voice)"
"16 kbps — Medium (recommended for Tor)"
"24 kbps — Medium-High (good quality)"
"32 kbps — High (wideband voice)"
"48 kbps — Very High (near-studio)"
"64 kbps — Maximum (best quality)"
)
for ((i = 0; i < ${#presets[@]}; i++)); do
local num=$((i + 1))
if [ "${presets[$i]}" = "$OPUS_BITRATE" ]; then
echo -e " ${GREEN}${BOLD}${num}${NC} ${CYAN}${NC} ${GREEN}${labels[$i]} ◄ current${NC}"
else
echo -e " ${BOLD}${WHITE}${num}${NC} ${CYAN}${NC} ${labels[$i]}"
fi
done
echo -e " ${BOLD}${WHITE}9${NC} ${CYAN}${NC} Custom bitrate"
echo -e " ${BOLD}${WHITE}0${NC} ${CYAN}${NC} ${DIM}Cancel${NC}"
echo ""
echo -ne " ${BOLD}Select: ${NC}"
read -r oinput
case "$oinput" in
[1-8])
OPUS_BITRATE=${presets[$((oinput - 1))]}
save_config
echo -e "\n ${GREEN}${BOLD}${NC} Opus bitrate set to ${WHITE}${BOLD}${OPUS_BITRATE} kbps${NC}"
;;
9)
echo -ne "\n ${BOLD}Enter bitrate (6-510 kbps): ${NC}"
read -r custom_br
if [[ "$custom_br" =~ ^[0-9]+$ ]] && [ "$custom_br" -ge 6 ] && [ "$custom_br" -le 510 ]; then
OPUS_BITRATE=$custom_br
save_config
echo -e "\n ${GREEN}${BOLD}${NC} Opus bitrate set to ${WHITE}${BOLD}${OPUS_BITRATE} kbps${NC}"
else
echo -e "\n ${RED}Invalid bitrate. Must be 6510.${NC}"
fi
;;
0|q|Q)
return
;;
*)
echo -e "\n ${RED}Invalid choice${NC}"
;;
esac
echo -ne " ${DIM}Press Enter to continue...${NC}"
read -r
}
#============================================================================= #=============================================================================
# MAIN MENU # MAIN MENU
#============================================================================= #=============================================================================
@ -1223,7 +1557,8 @@ show_banner() {
echo -e " ${TOR_PURPLE}───────────────────────────────────────${NC}" echo -e " ${TOR_PURPLE}───────────────────────────────────────${NC}"
echo -e " ${TOR_PURPLE}${BOLD}Encrypted Voice & Chat${NC} ${DIM}over${NC} ${TOR_PURPLE}${BOLD}Tor${NC} ${DIM}Hidden Services${NC}" echo -e " ${TOR_PURPLE}${BOLD}Encrypted Voice & Chat${NC} ${DIM}over${NC} ${TOR_PURPLE}${BOLD}Tor${NC} ${DIM}Hidden Services${NC}"
echo -e " ${TOR_PURPLE}───────────────────────────────────────${NC}" echo -e " ${TOR_PURPLE}───────────────────────────────────────${NC}"
echo -e " ${DIM}v${VERSION} | Push-to-Talk | End-to-End AES-256${NC}\n" local cipher_display="${CIPHER^^}"
echo -e " ${DIM}v${VERSION} | Push-to-Talk | End-to-End ${cipher_display}${NC}\n"
} }
main_menu() { main_menu() {
@ -1253,6 +1588,7 @@ main_menu() {
echo -e " ${BOLD}${WHITE}9${NC} ${CYAN}${NC} Stop Tor" echo -e " ${BOLD}${WHITE}9${NC} ${CYAN}${NC} Stop Tor"
echo -e " ${BOLD}${WHITE}10${NC}${CYAN}${NC} Restart Tor" echo -e " ${BOLD}${WHITE}10${NC}${CYAN}${NC} Restart Tor"
echo -e " ${BOLD}${WHITE}11${NC}${CYAN}${NC} Rotate onion address" echo -e " ${BOLD}${WHITE}11${NC}${CYAN}${NC} Rotate onion address"
echo -e " ${BOLD}${WHITE}12${NC}${CYAN}${NC} Settings"
echo -e " ${BOLD}${WHITE}0${NC} ${CYAN}${NC} ${RED}Quit${NC}" echo -e " ${BOLD}${WHITE}0${NC} ${CYAN}${NC} ${RED}Quit${NC}"
echo "" echo ""
echo -ne " ${BOLD}Select: ${NC}" echo -ne " ${BOLD}Select: ${NC}"
@ -1307,6 +1643,9 @@ main_menu() {
echo -ne "\n ${DIM}Press Enter to continue...${NC}" echo -ne "\n ${DIM}Press Enter to continue...${NC}"
read -r read -r
;; ;;
12)
settings_menu
;;
0|q|Q) 0|q|Q)
echo -e "\n${GREEN}Goodbye!${NC}" echo -e "\n${GREEN}Goodbye!${NC}"
stop_tor stop_tor