Skip to content

Instantly share code, notes, and snippets.

@gioxx
Last active October 28, 2025 15:59
Show Gist options
  • Select an option

  • Save gioxx/89a8520d1117114faaa1268f0c8f8740 to your computer and use it in GitHub Desktop.

Select an option

Save gioxx/89a8520d1117114faaa1268f0c8f8740 to your computer and use it in GitHub Desktop.
SMTP interactive tester wrapper for swaks (https://jetmore.org/john/code/swaks/)
#!/usr/bin/env bash
# SMTP interactive tester wrapper for swaks
# Gioxx, 2025 - https://github.com/gioxx
# - Checks and optionally installs swaks and Net::SSLeay (interactive)
# - Prompts the user for SMTP parameters with sensible defaults (Office365)
# - Builds a safe argument vector and runs swaks
# - Appends an RFC-like date to the Subject
set -euo pipefail
# Prompt with default value
prompt_default() {
local var_name="$1"; shift
local prompt_msg="$1"; shift
local default="$1"; shift
local reply
if [ -t 0 ]; then
read -r -p "$prompt_msg [$default]: " reply || reply=""
else
reply="$default"
fi
if [ -z "${reply}" ]; then
eval "$var_name=\"\$default\""
else
eval "$var_name=\"\$reply\""
fi
}
# Yes/No prompt (default yes = true)
prompt_yesno() {
local var_name="$1"; shift
local prompt_msg="$1"; shift
local default_yes="${1:-y}"; shift
local reply
if [ -t 0 ]; then
read -r -p "$prompt_msg ($([ "$default_yes" = "y" ] && echo "Y/n" || echo "y/N")): " reply || reply=""
else
reply="$default_yes"
fi
reply="${reply,,}" # to lowercase
if [ -z "$reply" ]; then
reply="$default_yes"
fi
if [[ "$reply" == "y" || "$reply" == "yes" ]]; then
eval "$var_name=true"
else
eval "$var_name=false"
fi
}
# Root or sudo helper
need_sudo() {
if [ "$(id -u)" -eq 0 ]; then
echo ""
elif command -v sudo >/dev/null 2>&1; then
echo "sudo"
else
echo ""
fi
}
# Detect basic OS / package manager
detect_pkg_env() {
# Outputs two values: PKG_MGR and FAMILY
# FAMILY: deb|rhel|alpine|mac|unknown
if [ "$(uname -s)" = "Darwin" ]; then
echo "brew mac"
return
fi
if [ -f /etc/os-release ]; then
. /etc/os-release
ID_LIKE="${ID_LIKE:-}"
case "$ID $ID_LIKE" in
*debian*|*ubuntu*|*Debian*|*Ubuntu*)
if command -v apt >/dev/null 2>&1 || command -v apt-get >/dev/null 2>&1; then
echo "apt deb"
return
fi
;;
*rhel*|*fedora*|*centos*|*rocky*|*almalinux*)
if command -v dnf >/dev/null 2>&1; then
echo "dnf rhel"; return
elif command -v yum >/dev/null 2>&1; then
echo "yum rhel"; return
fi
;;
*alpine*)
if command -v apk >/dev/null 2>&1; then
echo "apk alpine"; return
fi
;;
esac
fi
# Fallback unknown
echo "unknown unknown"
}
# Install swaks
install_swaks() {
local SUDO; SUDO="$(need_sudo)"
local PKG_MGR FAMILY; read -r PKG_MGR FAMILY < <(detect_pkg_env)
case "$FAMILY" in
deb)
$SUDO apt update
$SUDO apt install -y swaks
;;
rhel)
if [ "$PKG_MGR" = "dnf" ]; then
$SUDO dnf install -y swaks || $SUDO dnf install -y perl-App-cpanminus
else
$SUDO yum install -y swaks || $SUDO yum install -y perl-App-cpanminus
fi
;;
alpine)
$SUDO apk add --no-cache swaks
;;
mac)
if ! command -v brew >/dev/null 2>&1; then
echo "Homebrew not found. Install it from https://brew.sh and re-run." >&2
return 1
fi
brew install swaks
;;
*)
echo "Unsupported OS for auto-install of swaks. Please install swaks manually." >&2
return 1
;;
esac
}
# Install Net::SSLeay (and IO::Socket::SSL)
install_net_ssleay() {
local SUDO; SUDO="$(need_sudo)"
local PKG_MGR FAMILY; read -r PKG_MGR FAMILY < <(detect_pkg_env)
case "$FAMILY" in
deb)
$SUDO apt update
$SUDO apt install -y libnet-ssleay-perl libio-socket-ssl-perl
;;
rhel)
if [ "$PKG_MGR" = "dnf" ]; then
$SUDO dnf install -y perl-Net-SSLeay perl-IO-Socket-SSL
else
$SUDO yum install -y perl-Net-SSLeay perl-IO-Socket-SSL
fi
;;
alpine)
# Package names can vary by repo; try common ones:
$SUDO apk add --no-cache perl-net-ssleay perl-io-socket-ssl || {
echo "Falling back to cpan for Alpine ..." >&2
$SUDO apk add --no-cache perl make gcc g++ openssl-dev perl-dev
cpan -T -i Net::SSLeay IO::Socket::SSL
}
;;
mac)
if ! command -v brew >/dev/null 2>&1; then
echo "Homebrew not found. Install it from https://brew.sh and re-run." >&2
return 1
fi
# Try system Perl via CPAN (often the most reliable for these modules)
# Ensure Perl available (macOS has one), but we also prepare cpanminus if present
if command -v cpanm >/dev/null 2>&1; then
cpanm --notest Net::SSLeay IO::Socket::SSL
else
# cpan will ask first-time config; user interaction may be required
cpan -T -i Net::SSLeay IO::Socket::SSL
fi
;;
*)
echo "Unsupported OS for auto-install of Net::SSLeay. Please install it manually." >&2
return 1
;;
esac
}
# Checkers
have_swaks() { command -v swaks >/dev/null 2>&1; }
have_net_ssleay() { perl -MNet::SSLeay -e 'print $Net::SSLeay::VERSION' >/dev/null 2>&1; }
if ! have_swaks; then
echo "swaks not found."
prompt_yesno INSTALL_SWAKS "Install swaks now?" y
if [ "$INSTALL_SWAKS" = true ]; then
install_swaks || { echo "Failed to install swaks." >&2; exit 2; }
else
echo "Cannot proceed without swaks." >&2
exit 2
fi
fi
# Check Net::SSLeay now; if missing and user selects TLS later, we'll re-check and offer install again.
if ! have_net_ssleay; then
echo "Perl module Net::SSLeay not found (required for TLS)."
prompt_yesno INSTALL_SSL "Install Net::SSLeay (and IO::Socket::SSL) now?" y
if [ "$INSTALL_SSL" = true ]; then
install_net_ssleay || echo "Warning: failed to install Net::SSLeay. You can still use 'None' security."
fi
fi
echo "=== SMTP interactive swaks wrapper ==="
DEFAULT_HOST="smtp.office365.com"
DEFAULT_PORT="587"
DEFAULT_AUTH_METHOD="LOGIN" # LOGIN, PLAIN, CRAM-MD5
prompt_default HOST "SMTP server" "$DEFAULT_HOST"
prompt_default PORT "Port" "$DEFAULT_PORT"
echo "Choose security mode:"
echo " 1) STARTTLS (recommended for port 587)"
echo " 2) SMTPS (implicit TLS, typical port 465)"
echo " 3) None (plain, dev only)"
read -r -p "Select 1,2,3 [1]: " sec_choice
sec_choice=${sec_choice:-1}
case "$sec_choice" in
1) SECURITY="starttls" ;;
2) SECURITY="smtps" ;;
3) SECURITY="none" ;;
*) SECURITY="starttls" ;;
esac
# If TLS was chosen, ensure Net::SSLeay is present (offer install if still missing)
if [[ "$SECURITY" != "none" ]] && ! have_net_ssleay; then
echo "TLS selected but Net::SSLeay is missing."
prompt_yesno INSTALL_SSL2 "Install Net::SSLeay (and IO::Socket::SSL) now?" y
if [ "$INSTALL_SSL2" = true ]; then
install_net_ssleay || { echo "Failed to install Net::SSLeay; cannot proceed with TLS." >&2; exit 3; }
else
echo "Cannot proceed with TLS/SMTPS without Net::SSLeay. Choose 'None' or re-run." >&2
exit 3
fi
fi
prompt_yesno USE_AUTH "Use SMTP authentication?" y
if [ "$USE_AUTH" = true ]; then
read -r -p "Auth method (LOGIN/PLAIN/CRAM-MD5) [${DEFAULT_AUTH_METHOD}]: " AUTH_METHOD
AUTH_METHOD=${AUTH_METHOD:-$DEFAULT_AUTH_METHOD}
read -r -p "Username (user@domain): " USERNAME
if [ -t 0 ]; then
read -r -s -p "Password: " PASSWORD; echo
else
PASSWORD=""
fi
fi
prompt_default FROM "From address" "${USERNAME:-noreply@contoso.com}"
prompt_default TO "To address" "${FROM:-your.email@contoso.com}"
prompt_default SUBJECT "Subject base text" "swaks SMTP test"
echo "Enter message body — finish with Ctrl-D on an empty line:"
BODY=$(cat) # Ctrl-D to end; empty -> default below
DATE_STR=$(LC_TIME=C date +"%a, %d %b %Y %T %z")
FINAL_SUBJECT="${SUBJECT} ${DATE_STR}"
args=()
args+=(--to "$TO")
args+=(--from "$FROM")
args+=(--server "$HOST")
args+=(--port "$PORT")
case "$SECURITY" in
starttls) args+=(--tls) ;;
smtps) args+=(--smtps) ;;
none) : ;;
esac
if [ "$USE_AUTH" = true ]; then
args+=(--auth "$AUTH_METHOD")
args+=(--auth-user "$USERNAME")
if [ -z "${PASSWORD:-}" ] && [ -t 0 ]; then
read -r -s -p "Password (again): " PASSWORD; echo
fi
args+=(--auth-password "$PASSWORD")
fi
args+=(--header "Subject: $FINAL_SUBJECT")
if [ -z "$BODY" ]; then
BODY="Hello from swaks wrapper at $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
fi
args+=(--body "$BODY")
echo
echo "=== Summary ==="
echo "Server: $HOST:$PORT"
echo "Security: $SECURITY"
if [ "$USE_AUTH" = true ]; then
echo "Auth: yes (method: $AUTH_METHOD, user: $USERNAME)"
echo "Password: ******** (hidden)"
else
echo "Auth: no"
fi
echo "From: $FROM"
echo "To: $TO"
echo "Subject: $FINAL_SUBJECT"
echo "Body length: ${#BODY} bytes"
echo "================"
echo
prompt_yesno CONFIRM "Run swaks now?" y
if [ "$CONFIRM" != true ]; then
echo "Aborted by user."
exit 0
fi
echo "Running swaks ..."
exec swaks "${args[@]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment