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
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>&- |