You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

368 lines
10 KiB

#!/bin/bash
#
# --------------------------------
# VMODEM - Virtual Modem bootstrap
# --------------------------------
# Original by Oliver Molini 2021
# http://www.steptail.com/guides:virtual_modem:script
#
# Billy Stoughton II for bug fixes and contributions
#
# Modified and partially rewritten by Snep (contact-snep@diskcat.com) 2025
# for the CGHMN project
#
# Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
# https://creativecommons.org/licenses/by-nc-sa/4.0/
# Move to directory of script
cd "$(dirname "$0")" || exit
# Script version
vmodver=cghmn-1.0
# CONFIGURATION
# -----------------------
# Variable: SERIAL_PORT
# SERIAL_PORT specifies which local serial device to use.
# For example, "ttyUSB0" will tell the script to use
# to use /dev/ttyUSB0 for communication.
# Common values: ttyUSB0 or ttyAMA0
#
SERIAL_PORT="${SERIAL_PORT:-ttyUSB0}"
# Variable: BAUD
# BAUD will tell the script to open the serial port at
# specified symbol rate. When connecting, make sure
# your client computer uses the same BAUD than what
# has been specified here.
# Common BAUD rates: 9600, 19200, 38400, 57600
#
# Default:
# BAUD=57600
#
BAUD="${BAUD:-57600}"
# Variable: LOGIN_ENABLED
# If set to "yes", the LOGIN command is enabled,
# which allows remote users to log in to this
# machine with their Unix username and password
LOGIN_ENABLED="no"
# Variable: PPP_NUMBER
# If set to a number, when this number is called
# by the computer on the serial line, a PPP daemon
# will be spawned
PPP_DIAL_NUMBER="1"
# Variable: TERM
# Tells the script and environment which type of terminal to emulate.
# It is only useful to change this, if you're using a serial
# terminal to connect to this script. If you're connecting form a ANSI
# cabable machine such as DOS, you may want to use TERM="ansi"
#
TERM="vt100"
# EXPORT SHELL VARS
# -----------------
export SERIAL_PORT
export BAUD
export TERM
# FUNCTIONS
# ---------
#
#INITIALIZE SERIAL SETTINGS
ttyinit () {
exec 99>&-
stty -F "/dev/${SERIAL_PORT}" "${BAUD}"
stty -F "/dev/${SERIAL_PORT}" sane
stty -F "/dev/${SERIAL_PORT}" raw
stty -F "/dev/${SERIAL_PORT}" -echo -icrnl clocal
exec 99<>"/dev/${SERIAL_PORT}"
}
# SEND MESSAGE ON SCREEN AND OVER SERIAL
sendtty () {
NEWLINE_STR_CONSOLE="\n"
NEWLINE_STR_SERIAL="\x0d\x0a"
if [ "${1}" = "-n" ]; then
NEWLINE_STR_CONSOLE=""
NEWLINE_STR_SERIAL=""
shift
fi
echo -en "${1}${NEWLINE_STR_CONSOLE}";
echo -en "${1}${NEWLINE_STR_SERIAL}" > "/dev/${SERIAL_PORT}"
}
# Sends an AT status message or status code as reply
sendstatus () {
# Default to ERROR if not code given
RESULT_CODE="${1:-ERROR}"
if [ "${VERBOSITY:-verbose}" = "verbose" ]; then
sendtty "${RESULT_CODE}";
elif [ "${VERBOSITY}" = "numeric" ]; then
# Translate readable result codes into numeric ones if requested
if [ "${RESULT_CODE}" == "OK" ]; then sendtty 0
elif [ "${RESULT_CODE}" == "CONNECT" ]; then sendtty 1
elif [ "${RESULT_CODE}" == "RING" ]; then sendtty 2
elif [ "${RESULT_CODE}" == "NO CARRIER" ]; then sendtty 3
elif [ "${RESULT_CODE}" == "ERROR" ]; then sendtty 4
elif [ "${RESULT_CODE}" == "CONNECT 1200" ]; then sendtty 5
elif [ "${RESULT_CODE}" == "NO DIALTONE" ]; then sendtty 6
elif [ "${RESULT_CODE}" == "BUSY" ]; then sendtty 7
elif [ "${RESULT_CODE}" == "NO ANSWER" ]; then sendtty 8
else
echo "> Unspecified result code '${RESULT_CODE}'" >&2
sendtty "${RESULT_CODE}"
fi
fi
}
# Launch a script with getty
exec_with_getty () {
/sbin/getty -L "${SERIAL_PORT}" "${BAUD}" "${TERM}" -n -l "${1}"
return $?
}
# Dials a number (calls a number) from an ATD* command
dial () {
AT_COMMAND="${1}"
NUMBER="$( grep -Eo "[0-9]+" <<< "${AT_COMMAND}" )"
# Ensure number is not blank
if [ -z "${NUMBER}" ]; then
echo "> Blank number dialed" >&2
sendstatus "ERROR"
return 1
fi
DIAL_SCRIPT=""
# Show ringing status if verbose dialing is enabled
if [ "${VERBOSE_DIALING:-yes}" = "yes" ]; then
sendtty "RINGING"
sleep 1
fi
# Check if number is PPP dial number
if [ "${NUMBER}" = "${PPP_DIAL_NUMBER}" ]; then
if [ -f "${PWD}/ppp-cghmn.sh" ]; then
DIAL_SCRIPT="${PWD}/ppp-cghmn.sh"
elif [ -f "${PWD}/ppp.sh" ]; then
DIAL_SCRIPT="${PWD}/ppp.sh"
else
echo "> No pppd handler script found in '${PWD}'" >&2
sendstatus "NO CARRIER"
return
fi
else
# If not a PPP dial number, find script with called number
if [ -f "${PWD}/DIAL_${NUMBER}.sh" ]; then
DIAL_SCRIPT="${PWD}/DIAL_${NUMBER}.sh"
elif [ -f "${PWD}/${NUMBER}.sh" ]; then
DIAL_SCRIPT="${PWD}/${NUMBER}.sh"
else
sendstatus "NO CARRIER"
return
fi
fi
# For compatibility, explicitly tell the terminal to default to CR/LF
# when pressing enter, to avoid cases where the terminal just sends CR.
echo -en "\x1b[20h" > "/dev/${SERIAL_PORT}"
echo "> Executing dial script '${DIAL_SCRIPT}' for number '${NUMBER}'" >&2
# Show connect message
if [ "${VERBOSITY}" = "numeric" ]; then
sendtty "1"
elif [ "${VERBOSE_DIALING:-yes}" = "yes" ]; then
sendtty "CONNECT ${BAUD}"
else
sendtty "CONNECT"
fi
# Call script with getty
exec_with_getty "${DIAL_SCRIPT}"
echo "> Dial script returned with exit code ${?}" >&2
ttyinit
sendstatus "NO CARRIER"
}
export -f sendtty
export -f ttyinit
export -f sendstatus
# Open serial port for use. Allocate file descriptor
# and treat the serial port as a file.
ttyinit
#exec 99<>"/dev/${SERIAL_PORT}"
# Init message
sendtty ""
sendtty "VMODEM - Virtual Modem bootstrap for PPP link v${vmodver}"
sendtty "Connection speed set to ${BAUD} baud"
sendtty ""
sendtty "TYPE HELP FOR COMMANDS"
sendtty "READY."
# Main script loop
while true; do
CHARHEX="$(head -c 1 "/dev/${SERIAL_PORT}" | xxd -p -)"
CHAR="$(echo -e "\x${CHARHEX}")"
# Echo recevied CHARacter to console
echo -n "${CHAR}"
# Echo recevied CHARacter back to serial
if [ "${ECHO_SERIAL:-yes}" = "yes" ]; then
echo -n "${CHAR}" > "/dev/${SERIAL_PORT}"
fi
# Check for end of line on CR or LF
if [ "$CHARHEX" = "0d" ] || [ "$CHARHEX" = "0a" ]; then
# Upper-case entire line
cmd="${BUFFER^^}"
# Clear temporary BUFFERs
BUFFER=
CHAR=
# Write new line to console and serial
if [ "${ECHO_SERIAL:-yes}" = "yes" ]; then
sendtty ""
else
echo ""
fi
#
# --- HAYES EMULATION ---
#
if [[ $cmd == AT* ]]; then
# Handle known AT commands
case "$cmd" in
# Commands that do nothing but return OK
# <Blank line> shall also return OK
# ATA = Answer incoming call
# ATH0 = Modem Hang up (Go On-Hook)
# ATH1 = Modem Pick up (Go Off-Hook)
# ATM0 = Modem speaker always off
# ATM1 = Modem speaker on until carrier detected
# ATM2 = Modem speaker always on
# ATM3 = Modem speaker only on whilst answering
# AT&C0 = Force Carrier-Detect high
# AT&C1 = Let Modem set Carrier-Detect signal
# AT&D0 = Modem ignores DTR line from computer
# AT&D1 = Modem switches to AT mode on DTR on->off transition
# AT&D1 = Modem hangs up and switches to AT mode on DTR on->off transition
# AT&D3 = Modem resets itself on DTR on->off transition
# AT&S0 = Force DSR line high
# AT&S1 = Modem will set DSR high during connected state
# ATSn = Change current register to n
# ATSn=i = Change current register to n and store value i
""|AT|ATA|ATH*|ATM*|AT\&C*|AT\&S*|ATS*) ;;
# ATZ = Restore modem
# ATZn = Restore modem to profile n
# AT&F = Restore modem to factory settings
# AT&Fn = Restore modem to factory settings in profile n
ATZ*|AT\&F*)
ECHO_SERIAL="yes"
VERBOSITY="verbose"
;;
# ATE0 = Disable echo
# ATE1 = Enable echo
ATE|ATE0) ECHO_SERIAL="no" ;;
ATE1) ECHO_SERIAL="yes" ;;
# ATV0 = Disable verbose responses
# ATV1 = Enable verbose responses
ATV|ATV0) VERBOSITY="numeric" ;;
ATV1) VERBOSITY="verbose" ;;
# ATQ0 = Modem returns result codes
# ATQ1 = Modem is quiet, returns no result codes
ATQ|ATQ0) VERBOSITY="verbose" ;;
ATQ1) VERBOSITY="quiet" ;;
# ATX0 = Blind dial, no busy detection, CONNECT when connection established (Hayes Smartmodem 300 compatible result code)
# ATX1 = Blind dial, no busy detection, CONNECT with BAUD rate appended when connection established
# ATX2 = Dial tone detection, no busy detection, CONNECT with BAUD rate appened when connection established
# ATX3 = Blind dial, busy detection, CONNECT with BAUD rate appended when connection established
# ATX4 = Dial tone detection, busy detection, CONNECT with BAUD rate appended when connection established
ATX|ATX0) VERBOSE_DIALING="no" ;;
ATX1|ATX2|ATX3|ATX4) VERBOSE_DIALING="yes" ;;
# ATDn = Dial number
ATD*)
dial "${cmd}"
continue
;;
# Return ERROR on all other (unknown) commands
*)
sendstatus "ERROR"
continue
;;
esac
# Send OK if no other status code was returned earlier
sendstatus "OK"
elif [ "${cmd}" = "HELP" ]; then
sendtty "Command Reference for Virtual Modem Bootstrap v$vmodver"
sendtty ""
sendtty "AT......Tests modem link, prints OK if successful"
sendtty "ATE0....Switch terminal echo off"
sendtty "ATE1....Switch terminal echo on"
sendtty "ATD?....Fork program ?.sh and output on terminal"
sendtty "ATDT1...Open PPPD connection"
sendtty "ATZ.....Reset modem settings"
sendtty "HELP....Display command reference"
[ "${LOGIN_ENABLED}" = "yes" ] && \
sendtty "LOGIN...Fork a new linux login on serial"
sendtty "EXIT....End this script"
sendtty ""
sendtty "To establish connection over PPP, dial 1 using tone dialing (ATDT1)"
sendtty ""
sendtty "READY."
elif [ "${cmd}" = "LOGIN" ]; then
if [ "${LOGIN_ENABLED:-no}" = "yes" ]; then
# Spawn login command on serial if enabled above
exec 99>&-
/sbin/getty -L "${SERIAL_PORT}" "${BAUD}" "${TERM}"
# Reset tty after logout
ttyinit
sendtty ""
sendtty "READY."
else
sendtty "LOGIN is not enabled on this connection."
sendstatus "ERROR"
fi
elif [ "${cmd}" = "EXIT" ] || [ "${cmd}" = "QUIT" ]; then
# Exit script
# If managed by an init-system, this script *should* restart to serve the next connection
sendstatus "OK"
sendtty "BYE"
# Close serial port
exec 99>&-
exit 0
fi
else
# If not CR or LF, append CHARacter to BUFFER
BUFFER="${BUFFER}${CHAR}"
fi
done
# Close serial port
exec 99>&-