From e30a5c901bc1aeff926b42ae409ff84f9ee42932 Mon Sep 17 00:00:00 2001 From: Teal Dulcet Date: Sun, 31 Dec 2023 04:45:09 -0800 Subject: [PATCH] Added CI and updated Bash script. --- .github/workflows/ci.yml | 74 ++++++++++++++++++++++++++++ README.md | 43 +++++++++------- python-port/README.rst | 104 +++++++++++++++++++++++++++++++++++++-- sendmsg.sh | 39 ++++++++++----- 4 files changed, 223 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a9b8cc0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + pull_request: + schedule: + - cron: '0 0 1 * *' + +jobs: + Linux: + name: Linux Bash + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Script + run: | + bash -e -o pipefail -- sendmsg.sh -h + - name: ShellCheck + run: shopt -s globstar; shellcheck -o avoid-nullary-conditions,check-extra-masked-returns,check-set-e-suppressed,deprecate-which,quote-safe-variables,require-double-brackets -s bash **/*.sh + continue-on-error: true + + Pylint: + name: Pylint + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install pylint + - name: Script + run: pylint -f colorized --py-version 3.7 -d design,C0103,W0311,C0301,C0302,C0209 --load-plugins pylint.extensions.code_style,pylint.extensions.comparison_placement,pylint.extensions.for_any_all,pylint.extensions.consider_refactoring_into_while_condition,pylint.extensions.consider_ternary_expression,pylint.extensions.dict_init_mutate,pylint.extensions.docstyle,pylint.extensions.check_elif,pylint.extensions.set_membership,pylint.extensions.typing -e R6104,C1804,C1805 -r y python-port/ + continue-on-error: true + + Ruff: + name: Ruff + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install ruff + - name: Script + run: ruff --output-format=github --target-version py37 --select F,E4,E7,E9,W,I,D,UP,YTT,S,BLE,B,A,COM819,C4,T10,EM,EXE,ISC,ICN,G,PIE,PYI,Q,RSE,RET,SLF,SLOT,SIM,TID,TCH,ARG,PGH,PL,TRY,FLY,PERF,FURB,LOG,RUF --preview --ignore W191,D211,D213,D401,PLR09,PLR2004,RUF001,RUF002,RUF003 . + continue-on-error: true + + Python: + name: Linux Python + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.7", "pypy3.8", "pypy3.9", "pypy3.10"] + fail-fast: false + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Script + run: | + python -X dev python-port/sendpy/ --help + python -X dev python-port/sendpy/ --examples + python -X dev python-port/sendpy/ --smtp-servers + python -X dev python-port/sendpy/ --gateways diff --git a/README.md b/README.md index 351d35d..5fc688c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ +[![Actions Status](https://github.com/tdulcet/Send-Msg-CLI/workflows/CI/badge.svg?branch=master)](https://github.com/tdulcet/Send-Msg-CLI/actions) + # Send Msg CLI Send e-mail (and text messages) from the command line -Copyright © 2019 Teal Dulcet (Bash) and Daniel Connelly (Python) +Copyright © 2019 Teal Dulcet (both) and Daniel Connelly (Python) Send [e-mail](https://en.wikipedia.org/wiki/Email) (and [text messages](https://en.wikipedia.org/wiki/SMS)), with optional message and attachments, from the command line. Supports [Unicode characters](https://en.wikipedia.org/wiki/Unicode_and_email) in subject, message and attachment filename ([MIME](https://en.wikipedia.org/wiki/MIME)). Optionally use your own e-mail address and an external [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) server. Useful to know when a cron job failed, when a long running job (LRP) has finished, to quickly backup/share a file or to send notifications as part of a larger script. -❤️ Please visit [tealdulcet.com](https://www.tealdulcet.com/) to support these script and my other software development. +See the [python-port](python-port) directory for SendPy, a Python port of the script. + +❤️ Please visit [tealdulcet.com](https://www.tealdulcet.com/) to support these scripts and my other software development. ## Usage -Requires Bash 4.4 and the curl and netcat commands, which are included on most Linux distributions. +Requires at least Bash 4.4 and the curl and netcat commands, which are included on most Linux distributions. Optional [S/MIME](https://en.wikipedia.org/wiki/S/MIME) digital signatures require the openssl command.\ Optional [PGP/MIME](https://en.wikipedia.org/wiki/Pretty_Good_Privacy#OpenPGP) digital signatures require the gpg command. @@ -51,9 +55,9 @@ Example adapted from the [Linux Remote Servers Status Monitoring Script](https:/
Instructions -To send e-mail from a Gmail account, add these options to the command: `-f "Example " -S "smtps://smtp.gmail.com" -u "example@gmail.com" -p "PASSWORD"`. Or, open the script in an editor and set these variables near the top, where listed: +To send e-mail from a Gmail account, add these options to the command: `-f "User " -S "smtps://smtp.gmail.com" -u "example@gmail.com" -p "PASSWORD"`. Or, open the script in an editor and set these variables near the top, where listed: ```bash -FROMEMAIL="Example " +FROMEMAIL="User " SMTP="smtps://smtp.gmail.com" USERNAME="example@gmail.com" @@ -170,7 +174,7 @@ You will also need to either create an [App Password](https://support.google.com Supports International email addresses - ✔^ + ✔† ✔ ✔ @@ -242,8 +246,7 @@ You will also need to either create an [App Password](https://support.google.com \* Optional\ -^ Only supported in Internationalizing Domain Names in Applications (IDNA) encoding\ -^^ Does not work with all mobile providers +† Only supported in Internationalizing Domain Names in Applications (IDNA) encoding This is not a comprehensive list of the Send E-mail Script’s functionality. @@ -278,6 +281,8 @@ Options: -p SMTP server password -P Priority Supported priorities: "5 (Lowest)", "4 (Low)", "Normal", "2 (High)" and "1 (Highest)". Requires SMTP server. + -r Request Return Receipt + Requires SMTP server. -C S/MIME Certificate filename for digitally signing the e-mails It will ask you for the password the first time you run the script with this option. Requires SMTP server. -k PGP secret key passphrase for digitally signing the e-mails with PGP/MIME @@ -287,6 +292,7 @@ Options: Uses value of LANG environment variable. -U Sanitize the Date Uses Coordinated Universal Time (UTC) and rounds date down to whole minute. Set the TZ environment variable to change time zone. + -T Time to delay sending of the e-mail -d Dry run, do not send the e-mail -V Verbose, show the client-server communication Requires SMTP server. @@ -296,34 +302,34 @@ Options: Examples: Send e-mail - $ sendmsg -s "Example" -t "Example " + $ sendmsg -s "Example" -t "User " Send e-mail with message - $ sendmsg -s "Example" -m "This is an example!" -t "Example " + $ sendmsg -s "Example" -m "This is an example!" -t "User " Send e-mail with message and single attachment - $ sendmsg -s "Example" -m "This is an example!" -a example.txt -t "Example " + $ sendmsg -s "Example" -m "This is an example!" -a example.txt -t "User " Send e-mail with message and multiple attachments - $ sendmsg -s "Example" -m "This is an example!" -a example1.txt -a example2.txt -t "Example " + $ sendmsg -s "Example" -m "This is an example!" -a example1.txt -a example2.txt -t "User " Send e-mail to a CC address - $ sendmsg -s "Example" -t "Example 1 " -c "Example 2 " + $ sendmsg -s "Example" -t "User 1 " -c "User 2 " Send e-mail with a From address - $ sendmsg -s "Example" -f "Example " -t "Example " + $ sendmsg -s "Example" -f "Example " -t "User " Send e-mail with an external SMTP server - $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -t "Example " + $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -t "User " Send high priority e-mail - $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -P "1 (Highest)" -t "Example " + $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -P "1 (Highest)" -t "User " Send e-mail digitally signed with an S/MIME Certificate - $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -C "cert.p12" -t "Example " + $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -C "cert.p12" -t "User " Send e-mail digitally signed with PGP/MIME - $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -k "passphrase" -t "Example " + $ sendmsg -s "Example" -f "Example " -S "smtps://mail.example.com" -u "example" -p "password" -k "passphrase" -t "User " ``` @@ -343,7 +349,6 @@ Bash: Python: * Do not create temporary files for performance and to reduce disk wear. -* Support sanitizing the date. Both: * Improve the performance diff --git a/python-port/README.rst b/python-port/README.rst index f89d5d5..2b19506 100644 --- a/python-port/README.rst +++ b/python-port/README.rst @@ -8,7 +8,7 @@ However, sendpy is much more than that. sendpy is rich in features and use cases For example, using this program, one may: * Attach files to a message -* Send Unicode emojis in the Subject or Body of a message +* Send Unicode characters/emojis in the Subject or Body of a message * Digitally sign a message with S/MIME or PGP encryption * Send a notification when a long running process (LRP) has finished execution * Delay the sending of an email until a specific time @@ -20,13 +20,13 @@ Help for sending an e-mail and understanding this program can be found using `se To expedite the ability for users to send a message, here is a quick setup and example: -1. Install using the install command listed in PyPi. Be sure to use pip3. +1. Install: `pip3 install sendpy` or `python3 -m pip install sendpy`. -2. Set the default configuration file to store repetitive commands (e.g., username, password) with the command `sendpy --config`. +2. Set the default configuration file to store repetitive commands (e.g., username, password): `sendpy --config`. -3. Enable lower security settings in your external email client, if necessary (ex. [Gmail](https://myaccount.google.com/lesssecureapps)). +3. If necessary, create an App Password or enable less secure app access with your external email service (ex. [Gmail](https://myaccount.google.com/lesssecureapps)). -4. Send a basic message `sendpy --subject "Employment Opportunity" --to "connellyd2050@gmail.com" --message "Hello,\n\n When is a good time to talk about an open position we have for you?"`. +4. Send a basic message: `sendpy --subject "Employment Opportunity" --to "connellyd2050@gmail.com" --message "Hello,\n\n When is a good time to talk about an open position we have for you?"`. ## Dependencies The following libraries are used in this program: @@ -39,5 +39,99 @@ However, so long as a user is not signing emails (the `--passphrase` and `--cert one may use this program without any additional installations on Windows or macOS. +## Help +``` +$ sendpy --help +usage: [-h] [-v] [-s SUBJECT] [-m MESSAGE] [--message-file MESSAGE_FILE] [-a ATTACHMENTS] [-t TOEMAILS] [-c CCEMAILS] [-b BCCEMAILS] [-f FROMEMAIL] [-S SMTP] [--tls] [--starttls] [-u USERNAME] + [-p PASSWORD] [-P {5 Lowest),4 (Low),Normal,2 (High),1 (Highest}] [-r] [-C CERT] [-k PASSPHRASE] [-z ZIPFILE] [-l] [-U] [-T TIME] [-d] [-n NOTIFY] [-V] [--config] [--examples] + [--smtp-servers] [--gateways] + +One or more To, CC or BCC e-mail addresses are required. Send text messages by using the mobile providers e-mail to SMS or MMS gateway (see the --gateways option). See examples with the --examples +option. + +optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -s SUBJECT, --subject SUBJECT + Subject. Escape sequences are expanded. Supports Unicode characters. + -m MESSAGE, --message MESSAGE + Message body. Escape sequences are expanded. Supports Unicode characters. + --message-file MESSAGE_FILE + Message body from a file or standard input if the filename is '-'. + -a ATTACHMENTS, --attachment ATTACHMENTS + Attachment filename. Use multiple times for multiple attachments. Supports Unicode characters in filename. + -t TOEMAILS, --to TOEMAILS + To e-mail address. Use multiple times for multiple To e-mail addresses. + -c CCEMAILS, --cc CCEMAILS + CC e-mail address. Use multiple times for multiple CC e-mail addresses. + -b BCCEMAILS, --bcc BCCEMAILS + BCC e-mail address. Use multiple times for multiple BCC e-mail addresses. + -f FROMEMAIL, --from FROMEMAIL + From e-mail address + -S SMTP, --smtp SMTP SMTP server. Optionally include a port with the "hostname:port" syntax. Defaults to port 465 with --ssl/--tls and port 25 otherwise. Use "localhost" if running a mail + server on this device. + --tls Use a secure connection with SSL/TLS (Secure Socket Layer/Transport Layer Security) + --starttls Upgrade to a secure connection with StartTLS + -u USERNAME, --username USERNAME + SMTP server username + -p PASSWORD, --password PASSWORD + SMTP server password. For security, use the --config option instead for it to prompt you for the password and then store in the configuration file. + -P {5 (Lowest),4 (Low),Normal,2 (High),1 (Highest)}, --priority {5 (Lowest),4 (Low),Normal,2 (High),1 (Highest)} + Priority. Supported priorities: "5 (Lowest)", "4 (Low)", "Normal", "2 (High)" and "1 (Highest)" + -r, --receipt Request Return Receipt + -C CERT, --certificate CERT + S/MIME Certificate filename for digitally signing the e-mails. It will ask you for the password the first time you run the script with this option. + -k PASSPHRASE, --passphrase PASSPHRASE + PGP secret key passphrase for digitally signing the e-mails with PGP/MIME. For security, use 'config' for it to prompt you for the passphrase and then store in the + configuration file. + -z ZIPFILE, --zip ZIPFILE + Compress attachment(s) with zip + -l, --language Set Content-Language. Uses value of LANG environment variable on Linux. + -U, --sanitize-date Uses Coordinated Universal Time (UTC) and rounds date down to whole minute. + -T TIME, --time TIME Time to delay sending of the e-mail + -d, --dry-run Dry run, do not send the e-mail + -n NOTIFY, --notify NOTIFY + Run provided command and then send an e-mail with resulting output and exit code. + -V, --verbose Verbose, show the client-server communication + --config Store the --from, --smtp, --tls, --starttls, --username and --password option values in a '.sendpy.ini' configuration file as defaults for future use. It will prompt for + any values that are not provided. + --examples Show example usages of this script and exit + --smtp-servers Show a list of the SMTP servers for common e-mail services, then exit + --gateways Show a list the of SMS and MMS Gateways for common mobile providers in the United States and Canada, then exit +``` + +## Examples +``` +$ sendpy --examples +Sendpy Examples (assumes config file is used): + Send e-mail + $ sendpy --subject "Example" --to "User " + + Send e-mail with message + $ sendpy --subject "Example" --message 'This is an example!' --to "User " + + Send e-mail with message and single attachment + $ sendpy --subject "Example" --message 'This is an example!' --attachment example.txt --to "User " + + Send e-mail with message and multiple attachments + $ sendpy --subject "Example" --message 'This is an example!' --attachment example1.txt --attachment example2.txt --to "User " + + Send e-mail to a CC address + $ sendpy --subject "Example" --to "User 1 " --cc "User 2 " + + Set config file with external SMTP server + $ sendpy --from "Example " --smtp "mail.example.com" --tls --username "example" --config + + Send high priority e-mail + $ sendpy --subject "Example" --priority "1 (Highest)" --to "User " + + Send e-mail digitally signed with an S/MIME Certificate + $ sendpy --subject "Example" --certificate "cert.p12" --to "User " + + Send e-mail digitally signed with PGP/MIME + $ sendpy --subject "Example" --passphrase "config" --to "User " + +``` + ## Donate [PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=NJ4PULABRVNCC) diff --git a/sendmsg.sh b/sendmsg.sh index 031d608..f116240 100644 --- a/sendmsg.sh +++ b/sendmsg.sh @@ -19,7 +19,7 @@ set -e SEND=1 # To e-mail addresses -# Send SMSs by using your mobile providers e-mail to SMS or MMS gateway (https://en.wikipedia.org/wiki/SMS_gateway#Email_clients) +# Send text messages by using your mobile providers e-mail to SMS or MMS gateway (https://en.wikipedia.org/wiki/SMS_gateway#Email_clients) TOEMAILS=( ) @@ -148,34 +148,34 @@ Options: Examples: Send e-mail - $ $1 -s \"Example\" -t \"Example \" + $ $1 -s \"Example\" -t \"User \" Send e-mail with message - $ $1 -s \"Example\" -m \"This is an example"'!'"\" -t \"Example \" + $ $1 -s \"Example\" -m \"This is an example"'!'"\" -t \"User \" Send e-mail with message and single attachment - $ $1 -s \"Example\" -m \"This is an example"'!'"\" -a example.txt -t \"Example \" + $ $1 -s \"Example\" -m \"This is an example"'!'"\" -a example.txt -t \"User \" Send e-mail with message and multiple attachments - $ $1 -s \"Example\" -m \"This is an example"'!'"\" -a example1.txt -a example2.txt -t \"Example \" + $ $1 -s \"Example\" -m \"This is an example"'!'"\" -a example1.txt -a example2.txt -t \"User \" Send e-mail to a CC address - $ $1 -s \"Example\" -t \"Example 1 \" -c \"Example 2 \" + $ $1 -s \"Example\" -t \"User 1 \" -c \"User 2 \" Send e-mail with a From address - $ $1 -s \"Example\" -f \"Example \" -t \"Example \" + $ $1 -s \"Example\" -f \"Example \" -t \"User \" Send e-mail with an external SMTP server - $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -t \"Example \" + $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -t \"User \" Send high priority e-mail - $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -P \"1 (Highest)\" -t \"Example \" + $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -P \"1 (Highest)\" -t \"User \" Send e-mail digitally signed with an S/MIME Certificate - $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -C \"cert.p12\" -t \"Example \" + $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -C \"cert.p12\" -t \"User \" Send e-mail digitally signed with PGP/MIME - $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -k \"passphrase\" -t \"Example \" + $ $1 -s \"Example\" -f \"Example \" -S \"smtps://mail.example.com\" -u \"example\" -p \"password\" -k \"passphrase\" -t \"User \" " >&2 } @@ -343,7 +343,7 @@ encoded-word() { if [[ $1 =~ $RE ]]; then echo "$1" else - echo "=?utf-8?B?$(echo "${1@E}" | base64 -w 0)?=" + echo "=?utf-8?B?$(echo -n "${1@E}" | base64 -w 0)?=" fi } @@ -414,6 +414,11 @@ if [[ -n "$FROMADDRESS" ]] && ! [[ $FROMADDRESS =~ $RE1 && $FROMADDRESS =~ $RE2 fi if [[ -n "$CERT" ]]; then + if ! command -v openssl >/dev/null; then + echo "Error: OpenSSL is not installed." >&2 + exit 1 + fi + if [[ ! -r "$CERT" && ! -f "$CLIENTCERT" ]]; then echo "Error: '$CERT' certificate file does not exist." >&2 exit 1 @@ -422,7 +427,10 @@ if [[ -n "$CERT" ]]; then if [[ ! -s "$CLIENTCERT" ]]; then echo -e "Saving the client certificate from '$CERT' to '$CLIENTCERT'" echo -e "Please enter the password when prompted.\n" - openssl pkcs12 -in "$CERT" -out "$CLIENTCERT" -clcerts -nodes + if ! openssl pkcs12 -in "$CERT" -out "$CLIENTCERT" -clcerts -nodes; then + echo "Error saving the client certificate. Trying again in legacy mode." >&2 + openssl pkcs12 -in "$CERT" -out "$CLIENTCERT" -clcerts -nodes -legacy + fi fi # if ! output=$(openssl verify -verify_email "$FROMADDRESS" "$CLIENTCERT" 2>/dev/null); then @@ -451,6 +459,11 @@ if [[ -n "$CERT" ]]; then fi if [[ -n "$PASSPHRASE" ]]; then + if ! command -v gpg >/dev/null; then + echo "Error: GNU Privacy Guard is not installed." >&2 + exit 1 + fi + if ! echo "$PASSPHRASE" | gpg --pinentry-mode loopback --batch -o /dev/null -ab -u "$FROMADDRESS" --passphrase-fd 0 <(echo); then echo "Error: A PGP key pair does not yet exist for '$FROMADDRESS' or the passphrase was incorrect." >&2 exit 1