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
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:
- installare il pacchetto di Swaks nel caso in cui tu non lo abbia su sistema (parlo di distribuzioni Linux o macOS),
- installare la libreria Perl per sfruttare il TLS/SSL (Net::SSLeay),
- chiederti tutti i parametri necessari per effettuare il collegamento SMTP alla casella di posta elettronica che vuoi testare (di default ti proporrà i dati di collegamento a Office 365, ma solo per mia comodità),
- lanciare un test finale per permetterti di capire se c’è qualcosa che non va.
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[@]}" |
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
