V1.0.1 terminalphone.sh
This commit is contained in:
parent
40b5f81c51
commit
2dbb895cd7
1 changed files with 392 additions and 53 deletions
445
terminalphone.sh
445
terminalphone.sh
|
|
@ -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
|
||||||
fi
|
fi
|
||||||
echo -e " ${BOLD}Controls:${NC}"
|
|
||||||
echo -e " ${GREEN}[SPACE]${NC} -- Push-to-Talk"
|
# Save remote cipher for later redraws
|
||||||
echo -e " ${CYAN}[T]${NC} -- Send text message"
|
if [ -n "$remote_cipher" ]; then
|
||||||
echo -e " ${RED}[Q]${NC} -- Hang up"
|
echo "$remote_cipher" > "$remote_cipher_file"
|
||||||
echo -e ""
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1202,13 +1341,208 @@ show_status() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 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}PTT key: [SPACEBAR]${NC}"
|
echo -e " ${DIM}Opus frame: ${OPUS_FRAMESIZE}ms${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 6–510.${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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue