Skip to content

Commit

Permalink
Added more TLS options to resolve a common issue
Browse files Browse the repository at this point in the history
  • Loading branch information
JCoupalK committed May 15, 2024
1 parent 37b7dcc commit db4ce4b
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 74 deletions.
71 changes: 33 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
</p>

# Mail2Go - Lightweight CLI SMTP client

![License](https://img.shields.io/github/license/KeepSec-Technologies/Mail2Go)
![GitHub issues](https://img.shields.io/github/issues-raw/KeepSec-Technologies/Mail2Go)
![GitHub go.mod Go version (branch & subdirectory of monorepo)](https://img.shields.io/github/go-mod/go-version/KeepSec-Technologies/Mail2Go/main)
Expand All @@ -26,7 +27,7 @@ Mail2Go is a very lightweight command-line SMTP client written in Go, designed t
- **Attachments Support**: Attach multiple files of various types to your emails.
- **HTML and Plain Text**: Supports both HTML and plain text formats for email bodies.
- **Command Line Interface**: Easy-to-use CLI arguments for configuring and sending emails.
- **Flexible Configuration**: SMTP server, port, username, and password can be configured through CLI arguments or a JSON configuration file.
- **Flexible Configuration**: SMTP server, TLS, port, username, and password can be configured through CLI arguments or a JSON configuration file.

## Requirements

Expand All @@ -38,13 +39,13 @@ Mail2Go is a very lightweight command-line SMTP client written in Go, designed t
1. Download the binary with wget:

```shell
wget https://github.com/KeepSec-Technologies/Mail2Go/releases/download/1.1.3/mail2go_linux_amd64_1.1.3.tar.gz
wget https://github.com/KeepSec-Technologies/Mail2Go/releases/download/1.1.4/mail2go_linux_amd64_1.1.4.tar.gz
```

2. Unpack it with tar

```shell
tar -xf mail2go_linux_amd64_1.1.3.tar.gz
tar -xf mail2go_linux_amd64_1.1.4.tar.gz
```

3. Move it to your /usr/local/bin/ (Optional):
Expand All @@ -55,7 +56,7 @@ Mail2Go is a very lightweight command-line SMTP client written in Go, designed t

## Building from Source

1. Ensure you have Go installed on your system. You can download Go from [here](https://golang.org/dl/).
1. Ensure you have Go installed on your system. You can download Go from [here](https://go.dev/dl/).
2. Clone the repository:

```shell
Expand All @@ -71,66 +72,60 @@ Mail2Go is a very lightweight command-line SMTP client written in Go, designed t
4. Build the tool:

```shell
CGO_ENABLED=0 go build -a -installsuffix cgo -o mail2go .
go build -o mail2go .
```

## Usage

Run the Mail2Go tool with the required arguments:

```text
-s, --smtp-server SMTP server for sending emails
-p, --smtp-port SMTP server port (Default: 587)
-u, --smtp-username Username for SMTP authentication
-w, --smtp-password Password for SMTP authentication
-f, --from-email Email address to send from
-c, --config Path to the SMTP json config file which replaces the above arguments
-t, --to-email Email addresses that will receive the email, comma-separated
-h, --subject Subject of the email
-b, --body Body of the email
-af, --attachments File paths for attachments, comma-separated
-bf, --body-file File path for email body
```

Those arguments would look like this in your CLI:

```shell
./mail2go --smtp-server [SMTP_SERVER] --smtp-port [SMTP_PORT] --smtp-username [USERNAME] --smtp-password [PASSWORD] --from-email [FROM_EMAIL] --to-email [TO_EMAIL_1],[TO_EMAIL_2] --subject "Your Subject" --body "Your email body." --attachments "path/to/attachment1,path/to/attachment2"
```

or

```shell
./mail2go --config "path/to/config.json" --to-email [TO_EMAIL_1],[TO_EMAIL_2] --subject "Your Subject" --body-file "path/to/email.html" --attachments "path/to/attachment1,path/to/attachment2"
-s, --smtp-server SMTP server for sending emails
-p, --smtp-port SMTP server port (Default: 587)
-u, --smtp-username Username for SMTP authentication
-w, --smtp-password Password for SMTP authentication
-l, --tls-mode TLS mode (none, tls-skip, tls)
-f, --from-email Email address to send from
-c, --config Path to the SMTP json config file which replaces the above arguments
-t, --to-email Email addresses that will receive the email, can be multiples (comma-separated)
-h, --subject Subject of the email
-b, --body Body of the email
-af, --attachments File paths for attachments, comma-separated
-bf, --body-file File path for email body
```

## Examples

Basic example:

```shell
./mail2go -s mail.example.com -u [email protected] -w password123 -f [email protected] -t [email protected] -h 'Test Mail2Go Subject' -b 'This is a body!'
```
# Basic example:
./mail2go -s mail.example.com -p 587 -u [email protected] -w password123 -l tls -f [email protected] -t [email protected] -h 'Test Mail2Go Subject' -b 'This is a body!'
Example with two recipients, the body from an HTML file and two attached files (can be more):
# Example with two recipients, the body from an HTML file and two attached files (can be more):
./mail2go -s mail.example.com -p 587 -u [email protected] -w password123 -l tls -f [email protected] -t [email protected],[email protected] -h 'Test Mail2Go Subject' -bf demo/body.html -af README.md,demo/mail2go-smaller.png
```shell
./mail2go -s mail.example.com -u [email protected] -w password123 -f [email protected] -t [email protected],[email protected] -h 'Test Mail2Go Subject' -bf demo/body.html -af README.md,demo/mail2go-smaller.png
# Example without authentication and no TLS:
./mail2go -s mail.example.com -p 25 -l none -f [email protected] -t [email protected] -h 'Test Mail2Go Subject' -b 'This is a body!'
# Example of a local SMTP server with a TLS certificate that can't be verified:
./mail2go -s localhost -p 587 -u [email protected] -w password123 -l tls-skip -f [email protected] -t [email protected] -h 'Test Mail2Go Subject' -b 'This is a body!'
```

Example of json config file to pass to Mail2Go:
Example of json configuration file to pass to Mail2Go:

```json
{
"smtp_server": "mail.example.com",
"smtp_port": 587,
"smtp_username": "[email protected]",
"smtp_password": "password123",
"smtp_password": "password_example",
"tls_mode": "tls",
"from_email": "[email protected]"
}
```

The command that goes with it:
Example of the command that goes with it:

```shell
./mail2go -c demo/config.json -t [email protected] -h 'Test Mail2Go Subject' -b 'This is a body!'
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Config struct {
SMTPPort int `json:"smtp_port"`
SMTPUsername string `json:"smtp_username"`
SMTPPassword string `json:"smtp_password"`
TLSMode string `json:"tls_mode"`
FromEmail string `json:"from_email"`
}

Expand Down
3 changes: 2 additions & 1 deletion demo/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"smtp_server": "mail.example.com",
"smtp_port": 587,
"smtp_username": "[email protected]",
"smtp_password": "password123",
"smtp_password": "password_example",
"tls_mode": "tls",
"from_email": "[email protected]"
}
34 changes: 34 additions & 0 deletions demo/postfix/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Use an official Debian image as a parent image
FROM debian:12

# Set noninteractive installation to avoid prompts
ENV DEBIAN_FRONTEND=noninteractive

# Install Postfix
RUN apt-get update && apt-get install -y postfix

# Copy your TLS certificates into the container
COPY smtp.crt /etc/ssl/certs/smtp.crt
COPY smtp.key /etc/ssl/private/smtp.key

# Setup Postfix configuration
RUN postconf -e "myhostname = mydomain.com" \
&& postconf -e "smtpd_tls_cert_file = /etc/ssl/certs/smtp.crt" \
&& postconf -e "smtpd_tls_key_file = /etc/ssl/private/smtp.key" \
&& postconf -e "smtpd_tls_security_level = may" \
&& postconf -e "smtpd_tls_auth_only = no" \
&& postconf -e "smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination" \
&& postconf -e "mydestination = $myhostname, localhost.$mydomain, localhost" \
&& postconf -e "mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 0.0.0.0/0" \
&& postconf -e "inet_interfaces = all" \
&& postconf -e "inet_protocols = ipv4" \
&& postconf -e "smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination" \
&& postconf -e "smtp_bind_address = 0.0.0.0" \
&& postconf -e "smtpd_sasl_auth_enable = no"

# Expose ports for SMTP
EXPOSE 25

# Start Postfix
CMD ["postfix", "start-fg"]

22 changes: 22 additions & 0 deletions demo/postfix/smtp.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDlTCCAn2gAwIBAgIUahX9BP6n28j7ZDrnVwpBZMXA+J8wDQYJKoZIhvcNAQEL
BQAwWjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5
MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xFTATBgNVBAMMDG15ZG9tYWluLmNvbTAe
Fw0yNDA1MTQyMjEzNThaFw0yNTA1MTQyMjEzNThaMFoxCzAJBgNVBAYTAlVTMQ4w
DAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UECgwMT3JnYW5pemF0
aW9uMRUwEwYDVQQDDAxteWRvbWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQCxZHmz4tCnqZO5h4WMg1eacFbk8FdxrR7VBWx9vX7GoDdcX+OO
UpyLKdFtYqDquqfp2/utwa1XYiVLzbNgFGLsDD3b+dLsJv+Cux9ev52dsfpYUrN8
tinEhpqPR/VT2dAOb1O1RUhi20vyZbQvjFbMF3knef10C6ulEr+H7awIInAqrzSR
+XSSeLSEzH8AW1sGpJUsvZBw+Cz7rCQh0+bLdu5tvb/rUR2a3N4sYPZsByw55dRk
XpggX7rPrAsgBrJnHcj5dLS8HynmaEvqBUnOvPaMwkWGrSoNNABHKVWfzyb574ak
ul/I62c/FzmA+U2LYYpVsBNi6FtlFhzlSm7tAgMBAAGjUzBRMB0GA1UdDgQWBBTZ
xuWPSsBT6oLNEOgnk3P0w1g1LjAfBgNVHSMEGDAWgBTZxuWPSsBT6oLNEOgnk3P0
w1g1LjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQARWfU47YQ7
AyjYos/Aj4OEYLrfTS81aDsJMycmZNZ13D80EC8PB+GvB20wwcdEIOzkSSe6XlX/
RScwctYaEiAg6E3Xsm7HW5tGRbau0r//OMHEiEGqdUWLLnS+wvqYkdBunJTr1fz4
pS8x4B13DIdQaMIs3oFU+MCu/t2IZmzPHGZMSgKMtxKFaz5QOMTfKWKkNIq82V0P
aB0/X2Q3I0WvRiyN2LWtowFKjrg+23qH0gtClcjd42YLLP+m0c/+2ASMU/H+RMaD
6JrC+HzVY6IOMnsm/mqa7Xy2fFJ0dhMhXNKpjod4MhjYp29x+LAIxzxnDH38Vo/1
2U0mVjmBjdBf
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions demo/postfix/smtp.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxZHmz4tCnqZO5
h4WMg1eacFbk8FdxrR7VBWx9vX7GoDdcX+OOUpyLKdFtYqDquqfp2/utwa1XYiVL
zbNgFGLsDD3b+dLsJv+Cux9ev52dsfpYUrN8tinEhpqPR/VT2dAOb1O1RUhi20vy
ZbQvjFbMF3knef10C6ulEr+H7awIInAqrzSR+XSSeLSEzH8AW1sGpJUsvZBw+Cz7
rCQh0+bLdu5tvb/rUR2a3N4sYPZsByw55dRkXpggX7rPrAsgBrJnHcj5dLS8Hynm
aEvqBUnOvPaMwkWGrSoNNABHKVWfzyb574akul/I62c/FzmA+U2LYYpVsBNi6Ftl
FhzlSm7tAgMBAAECggEACKhm72p0u2O7MjhOdbliiPobDTKhwfDGHkJIG91bMHLy
k/XMhs5UL5wx4xnUnfNp08mI/6w1O2kurGpBhMEmw6IkhSj5rDO9dfIpQzGTWsMs
Y17EV3DfAJiyA6fQ1G/Y1LhTdV4IfP96nHEHgBtkXRlEMExztbps45ifp0Qbu/Ke
+Q1OxsF+i3Bu71rFrnV9iOD1crLDlH1ZX686tpAShYwObQwc/nuzLaiICXfPwyI2
0QJB4Q12o5mrfKhAUzePZEiwLdRA8NOA4lRbTH9OjYoSa9tN63czxrH18BoeSed0
n85t2UY92qjDCS3RdjjvUAMAU5s6LmaXz3BWDLvfDQKBgQDxzsIzwxF2ZjVn5ECQ
W0yHyKPPxCbeQeMzFmkQ9pweK0iVooTmR5crr2s0LylMxKX8qMWbfI9ZoVOZYqU2
JipmV23WlCw246tbkSeaQqlvNQzdx2hmJ/lDiJVSUWVAYkoZMZisPMIPZCtuWM3W
Sv6e0fYCDuFkpZKP8sii+WDrbwKBgQC7zdtw8r2Akj7JlCokfWUSR75pdbKKvQWO
a/4iBiF+xJEUiBwM3LxbbNoR8Eu/w43JCksAgQh+bFOUL5jE6l/8bEPxTDrGp6zt
GIjuaXmrwpmLyXRuFzzaYH9BUMLiGJyvl6hfVgTWVQODoxGl3Fs1v2XGVcspeqLR
zHlwvnFNYwKBgQC9TdvVwDJsmenXkudE3GUWrGoqXur692QSe8n3YMmqCMLDer9G
tOdRaPyplv5jPlSgb9R8PNDRH66eF481zD1Hb8zqv4e51RUzE3mImAWjrUmMWu+N
gl/vkf8sudJlzE0sWhqnRM28VPR3aAiFaqLZ3ZAV3mZwb/tEvJJ8nHVW+wKBgFAC
E0q7HyB3LWiTRqDlCvoOtoAXNEkG3iceutWj5wEqnOQyWEDiiRwp831Q8fRwSycq
y2kbj5LMc47d+Cdr8hiHxyo1X5TiOjmICk4HgV47OU7kNEXygGpIUe5xiZTpB1eH
NKPo8YaeETEzd4FBr1nmgGVOh47UQClBAzuU5pAhAoGBAOqVG+Gkx4ecOASXlG+r
nyBVKvFqfOOHlNViZ622IC9nBB7bSJ3QAnDeNMThC1/wvMlnziFLfaWnu3ciiKNq
VS+MNccVscIn3aOzKJTdvzXPaLPXd7XJZT4BUpBDNAeBmnfxMNdQbj7lJQTrVFm9
MhKbE3mdcIDspS7gH+TFPy3R
-----END PRIVATE KEY-----
31 changes: 23 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"flag"
"fmt"
"log"
"os"
"strings"
)
Expand All @@ -15,6 +14,8 @@ var (
username string
password string

tlsMode string

configFile string

fromEmail string
Expand All @@ -32,6 +33,8 @@ var (
usernameShort string
passwordShort string

tlsModeShort string

configFileShort string

fromEmailShort string
Expand All @@ -51,6 +54,8 @@ func init() {
flag.StringVar(&username, "smtp-username", "", "Username for SMTP authentication")
flag.StringVar(&password, "smtp-password", "", "Password for SMTP authentication")

flag.StringVar(&tlsMode, "tls-mode", "", "TLS mode (none, tls-skip, tls)")

flag.StringVar(&configFile, "config", "", "Path to the SMTP config file")

flag.StringVar(&fromEmail, "from-email", "", "Email address to send from")
Expand All @@ -68,6 +73,8 @@ func init() {
flag.StringVar(&usernameShort, "u", "", "Username for SMTP authentication (short)")
flag.StringVar(&passwordShort, "w", "", "Password for SMTP authentication (short)")

flag.StringVar(&tlsModeShort, "l", "", "TLS mode (short)")

flag.StringVar(&configFileShort, "c", "", "Path to the SMTP config file (short)")

flag.StringVar(&fromEmailShort, "f", "", "Email address to send from (short)")
Expand All @@ -81,6 +88,8 @@ func init() {
}

func main() {
// Override the default flag.Usage
flag.Usage = Usage
flag.Parse()

// Override long-form flags with short-form flags if set
Expand All @@ -96,6 +105,9 @@ func main() {
if passwordShort != "" {
password = passwordShort
}
if tlsModeShort != "" {
tlsMode = tlsModeShort
}
if configFileShort != "" {
configFile = configFileShort
}
Expand All @@ -122,7 +134,7 @@ func main() {
if configFile != "" {
config, err := loadConfig(configFile)
if err != nil {
log.Fatalf("Error loading config file: %v", err)
fmt.Printf("Error loading config file: %v", err)
}

// Override flags with config file values if set
Expand All @@ -138,28 +150,31 @@ func main() {
if config.SMTPPassword != "" {
password = config.SMTPPassword
}
if config.TLSMode != "" {
tlsMode = config.TLSMode
}
if config.FromEmail != "" {
fromEmail = config.FromEmail
}
}

// Check if required flags or config values are missing
if smtpServer == "" || username == "" || password == "" || fromEmail == "" || toEmail == "" || subject == "" {
if smtpServer == "" || tlsMode == "" || fromEmail == "" || toEmail == "" || subject == "" {
fmt.Fprintln(os.Stderr, "Error: Required flags or config values are missing.")
usage()
Usage()
}

// Check if either direct input or file path is provided for body
if body == "" && bodyFile == "" {
fmt.Fprintln(os.Stderr, "Error: Subject and body are required, either directly or through a specified file.")
usage()
Usage()
}

// Read body from files if provided
if bodyFile != "" {
content, err := os.ReadFile(bodyFile)
if err != nil {
log.Fatalf("\nError reading body file: %v\n", err)
fmt.Printf("\nError reading body file: %v\n", err)
}
body = string(content)
}
Expand All @@ -178,8 +193,8 @@ func main() {

if len(toEmails) == 0 {
fmt.Fprintln(os.Stderr, "Error: At least one recipient email address is required.")
usage()
Usage()
}

sendEmail(smtpServer, smtpPort, username, password, fromEmail, toEmails, subject, body, bodyFile, attachmentPaths)
sendEmail(smtpServer, smtpPort, username, password, fromEmail, toEmails, subject, body, bodyFile, attachmentPaths, tlsMode)
}
Loading

0 comments on commit db4ce4b

Please sign in to comment.