Site icon Gioxx.org

Swaks: un wrapper in bash per verificare il tuo server SMTP

Office 365: salvare una copia della Posta Inviata di una Shared Mailbox 3

Un po’ di cultura generale e spicciola. Cos’è Swaks (Swiss Army Knife for SMTP)?

Swaks is a featureful, flexible, scriptable, transaction-oriented SMTP test tool written and maintained by John Jetmore. It is free to use and licensed under the GNU GPLv2.

Features include:

  • SMTP extensions including TLS, authentication, pipelining, PROXY, PRDR, and XCLIENT
  • Protocols including SMTP, ESMTP, and LMTP
  • Transports including UNIX-domain sockets, internet-domain sockets (IPv4 and IPv6), and pipes to spawned processes
  • Completely scriptable configuration, with option specification via environment variables, configuration files, and command line

jetmore.org/john/code/swaks

Avevo necessità di verificare che una casella di posta elettronica creata sul tenant Exchange (Microsoft 365) funzionasse correttamente, considerato che l’ho limitata in tutto tranne nell’invio tramite SMTP autenticato. Ho quindi messo in campo Swaks e, dato che non ricordavo a memoria i parametri, ho consultato la documentazione e li ho trovati, usati e verificato il corretto funzionamento della casella.

Al termine, però, ho pensato che un wrapper avrebbe reso la vita certamente più comoda. Considerato che quasi sicuramente tornerò a usare Swaks prossimamente, ho buttato giù qualche riga di codice per:

Ho cercato di mantenere quanti più suggerimenti di default possibili, ma chiaramente alcune richieste sono imprenscindibili. Il codice è disponibile pubblicamente su Gist:

#!/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[@]}"
view raw swaksWrapper.sh hosted with ❤ by GitHub

Puoi facilmente scaricarlo tramite wget sulla macchina dove intendi utilizzarlo:

wget https://go.gioxx.org/swakswrapper -O swaksWrapper.sh

In caso di problemi

Mi è capitato di avere problemi nel download di pacchetti (ma anche dello script stesso da GitHub) causati da proxy aziendali un po’ più restrittivi. Nel caso in cui tu stia ricevendo un errore per certificato non valido quando lanci il wget, puoi ricorrere al parametro --no-check-certificate così da evitare la verifica su player che solitamente si mettono in mezzo alla comunicazione tra la tua postazione e la destinazione finale, perciò:

wget --no-check-certificate https://go.gioxx.org/swakswrapper -O swaksWrapper.sh

Se hai dubbi, l’area commenti è a tua totale disposizione! 🙂👋

#KeepItSimple

Correzioni, suggerimenti? Lascia un commento nell'apposita area qui di seguito o contattami privatamente.
Ti è piaciuto l'articolo? Offrimi un caffè! ☕ :-)

Exit mobile version