Compare commits

...

1 commit

Author SHA1 Message Date
02ab2ec867
Refactor in multiple files. First working version. 2020-02-10 21:51:49 +01:00
8 changed files with 425 additions and 162 deletions

126
config.go Normal file
View file

@ -0,0 +1,126 @@
package main
import (
"errors"
"fmt"
"os"
toml "github.com/pelletier/go-toml"
)
type Validation struct {
Param string
CmdFlag string
ConfFlag string
}
type ServerConfig struct {
Address string `toml:address,omitempty`
Port int64 `toml:port,omitempty`
Encryption bool `toml:encryption,omitempty`
User string `toml:user,omitempty`
Password string `toml:password,omitempty`
}
func (s ServerConfig) String() string {
return fmt.Sprintf(
"\tAddress: %s\n\tPort: %d\n\tEncryption: %t\n\tUser: %s\n\tPassword: %s\n",
s.Address,
s.Port,
s.Encryption,
s.User,
s.Password,
)
}
type Config struct {
Server *ServerConfig `toml:server,omitempty`
From string `toml:from,omitempty`
To []string `toml:to,omitempty`
Cc []string `toml:cc,omitempty`
Bcc []string `toml:bcc,omitempty`
Subject string `toml:subject,omitempty`
Text string `toml:text,omitempty`
}
func (c Config) String() string {
return fmt.Sprintf(
"From: %s\nTo: %s\nCc: %s\nBcc: %s\nSubject: %s\nText:\n%s\nServer:\n%s",
c.From,
c.To,
c.Cc,
c.Bcc,
c.Subject,
c.Text,
c.Server,
)
}
func NewConfig() *Config {
server := &ServerConfig{}
config := &Config{}
config.Server = server
return config
}
func (s *ServerConfig) Merge(key string, value interface{}) {
Merge(key, value, s)
}
func (c *Config) Merge(key string, value interface{}) {
Merge(key, value, c)
}
func readConfig(configPath, section string) *Config {
config := NewConfig()
tree, err := toml.LoadFile(configPath)
if err != nil {
Error.F("Error in parsing the configuration\n%s", err)
os.Exit(2)
}
keys := tree.Keys()
if !IsPresent(keys, section) {
return config
}
sub := tree.Get(section).(*toml.Tree)
err = sub.Unmarshal(config)
return config
}
func checkValidity(validations *[]Validation, obj interface{}, name, cmd, param string) {
if IsEmpty(obj) {
*validations = append(*validations, Validation{name, cmd, param})
}
}
func (c *Config) Validate() error {
var validations = []Validation{}
var msg string
checkValidity(&validations, c.Server.Address, "server address", "server-address", "server.address")
checkValidity(&validations, c.Server.Port, "server port", "server-port", "server.port")
if !c.Server.Encryption {
Warning.Ln("Warning: not using encryption!")
}
checkValidity(&validations, c.Server.User, "username", "user", "server.user")
checkValidity(&validations, c.Server.Password, "password", "password", "server.password")
checkValidity(&validations, c.From, "from", "from", "from")
checkValidity(&validations, c.To, "to", "to", "to")
checkValidity(&validations, c.Subject, "subject", "sub", "subject")
checkValidity(&validations, c.Text, "body", "", "text")
Debug.F("Lengths:\n\tTo: %d\n\tCc: %d\n\tBcc: %d", len(c.To), len(c.Cc), len(c.Bcc))
if len(validations) > 0 {
for _, v := range validations {
if v.CmdFlag == "" {
msg += fmt.Sprintf("%s: pass a value either via stdin or in configuration file section (%s)\n", v.Param, v.ConfFlag)
} else {
msg += fmt.Sprintf("%s: pass a value either via command line (-%s) or in configuration file section (%s)\n", v.Param, v.CmdFlag, v.ConfFlag)
}
}
return errors.New(msg)
}
return nil
}

10
go.mod Normal file
View file

@ -0,0 +1,10 @@
module git.lattuga.net/blallo/sendmail
go 1.13
require (
github.com/fatih/color v1.9.0
github.com/pelletier/go-toml v1.6.0
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1
)

24
go.sum Normal file
View file

@ -0,0 +1,24 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

96
log.go Normal file
View file

@ -0,0 +1,96 @@
package main
import (
"io"
"io/ioutil"
"log"
"os"
"github.com/fatih/color"
)
type DebugLog struct {
logger *log.Logger
}
type InfoLog struct {
logger *log.Logger
}
type WarningLog struct {
logger *log.Logger
}
type ErrorLog struct {
logger *log.Logger
}
var (
Debug = &DebugLog{}
Info = &InfoLog{}
Warning = &WarningLog{}
Error = &ErrorLog{}
)
func (l *DebugLog) F(fmt string, text ...interface{}) {
l.logger.Print(color.MagentaString(fmt, text...))
}
func (l *DebugLog) Ln(text ...interface{}) {
l.logger.Print(color.MagentaString("%s", text...))
}
func (l *InfoLog) F(fmt string, text ...interface{}) {
l.logger.Print(color.WhiteString(fmt, text...))
}
func (l *InfoLog) Ln(text ...interface{}) {
l.logger.Print(color.WhiteString("%s", text...))
}
func (l *WarningLog) F(fmt string, text ...interface{}) {
l.logger.Print(color.YellowString(fmt, text...))
}
func (l *WarningLog) Ln(text ...interface{}) {
l.logger.Print(color.YellowString("%s", text...))
}
func (l *ErrorLog) F(fmt string, text ...interface{}) {
l.logger.Print(color.RedString(fmt, text...))
}
func (l *ErrorLog) Ln(text ...interface{}) {
l.logger.Print(color.RedString("%s", text...))
}
func (l *DebugLog) init(w io.Writer, prefix string, flag int) {
l.logger = &log.Logger{}
l.logger = log.New(w, prefix, flag)
}
func (l *InfoLog) init(w io.Writer, prefix string, flag int) {
l.logger = &log.Logger{}
l.logger = log.New(w, prefix, flag)
}
func (l *WarningLog) init(w io.Writer, prefix string, flag int) {
l.logger = &log.Logger{}
l.logger = log.New(w, prefix, flag)
}
func (l *ErrorLog) init(w io.Writer, prefix string, flag int) {
l.logger = &log.Logger{}
l.logger = log.New(w, prefix, flag)
}
func LogInit(debug bool) {
var debugOut io.Writer
if debug {
debugOut = os.Stdout
} else {
debugOut = ioutil.Discard
}
Debug.init(debugOut, "", 0)
Info.init(os.Stdout, "", 0)
Warning.init(os.Stdout, "", 0)
Error.init(os.Stderr, "", 0)
}

45
mail.go Normal file
View file

@ -0,0 +1,45 @@
package main
import (
"os"
mail "gopkg.in/mail.v2"
)
func formatMessage(config *Config) *mail.Message {
m := mail.NewMessage()
m.SetHeader("From", config.From)
m.SetHeader("To", config.To...)
if !IsEmpty(config.Cc) {
m.SetHeader("Cc", config.Cc...)
}
if !IsEmpty(config.Bcc) {
m.SetHeader("Bcc", config.Bcc...)
}
m.SetHeader("Subject", config.Subject)
m.SetBody("text/plain", config.Text)
Debug.F("Message to deliver:\n%s", m)
return m
}
func deliverMessage(s *ServerConfig, m *mail.Message) error {
dialer := mail.NewDialer(
s.Address,
int(s.Port),
s.User,
s.Password,
)
if s.Encryption {
dialer.StartTLSPolicy = mail.MandatoryStartTLS
}
return dialer.DialAndSend(m)
}
func SendMail(config *Config) {
m := formatMessage(config)
if err := deliverMessage(config.Server, m); err != nil {
Error.F("Delivery failure:\n%s", err)
os.Exit(3)
}
}

211
main.go
View file

@ -1,155 +1,47 @@
package main
import (
"bufio"
"flag"
"fmt"
"log"
"reflect"
"os"
"strings"
toml "github.com/pelletier/go-toml"
// mail "gopkg.in/gomail.v2"
)
type ServerConfig struct {
Address string `toml:address,omitempty`
Port int64 `toml:port,omitempty`
Encryption string `toml:encryption,omitempty`
User string `toml:user,omitempty`
Password string `toml:password,omitempty`
func readFromConsole() string {
var text, line string
var err error
counter := 0
reader := bufio.NewReader(os.Stdin)
for counter < 3 {
line, err = reader.ReadString('\n')
if line == "\n" {
counter += 1
}
func (s ServerConfig) String() string {
return fmt.Sprintf(
"\tAddress: %s\n\tPort: %d\n\tEncryption: %s\n\tUser: %s\n\tPassword: %s\n",
s.Address,
s.Port,
s.Encryption,
s.User,
s.Password,
)
text += fmt.Sprintf("%s\n", line)
}
type Config struct {
Server *ServerConfig `toml:server,omitempty`
From string `toml:from,omitempty`
To []string `toml:to,omitempty`
Cc []string `toml:cc,omitempty`
Bcc []string `toml:bcc,omitempty`
Subject string `toml:subject,omitempty`
Text string `toml:text,omitempty`
}
func (c Config) String() string {
return fmt.Sprintf(
"From: %s\nTo: %s\nCc: %s\nBcc: %s\nSubject: %s\nText:\n%s\nServer:\n%s",
c.From,
c.To,
c.Cc,
c.Bcc,
c.Subject,
c.Text,
c.Server,
)
}
func NewConfig() *Config {
server := &ServerConfig{}
config := &Config{}
config.Server = server
return config
}
func debug(active bool, fmt string, msg ...interface{}) {
if active {
log.Printf(fmt, msg...)
}
}
func isPresent(slice []string, val string) bool {
for _, item := range slice {
if item == val {
return true
}
}
return false
}
func isEmpty(value interface{}) bool {
t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Slice:
return v.Len() == 1
default:
return value == reflect.Zero(t).Interface()
}
}
func readConfig(configPath, section string) *Config {
config := NewConfig()
tree, err := toml.LoadFile(configPath)
if err != nil {
log.Panicln(err)
Error.F("Error in reading text from console\n%s", err)
os.Exit(1)
}
keys := tree.Keys()
if !isPresent(keys, section) {
return config
}
sub := tree.Get(section).(*toml.Tree)
err = sub.Unmarshal(config)
return config
return text
}
func (c *Config) Validate() error {
return nil
}
func merge(key string, value, obj interface{}) {
if isEmpty(value) {
return
}
var elem reflect.Value
if reflect.TypeOf(obj) != reflect.TypeOf(reflect.Value{}) {
log.Println("Not Value")
elem = reflect.ValueOf(obj).Elem()
} else {
log.Println("Value")
elem = obj.(reflect.Value)
}
field := elem.FieldByName(key)
if field.CanSet() {
switch field.Kind() {
case reflect.Int64:
field.SetInt(value.(int64))
case reflect.Bool:
field.SetBool(value.(bool))
case reflect.String:
field.SetString(value.(string))
case reflect.Slice:
field.Set(reflect.ValueOf(value))
default:
merge(key, reflect.ValueOf(value), field)
//merge(key, value, field.Pointer())
func toList(input string) []string {
var result = []string{}
list := strings.Split(input, ",")
for _, element := range list {
if element != "" {
result = append(result, element)
}
}
}
func (s *ServerConfig) Merge(key string, value interface{}) {
merge(key, value, s)
}
func (c *Config) Merge(key string, value interface{}) {
merge(key, value, c)
return result
}
func main() {
//var err error
var configPath, section, serverAddress, encryption, user, password, to, cc, bcc, from, subject string
var dbg bool
var err error
var configPath, section, serverAddress, user, password, to, cc, bcc, from, subject, text string
var encryption, dbg bool
var serverPort_ int
var serverPort int64
flag.StringVar(&configPath, "conf", "/etc/sendmail.toml", "Path to a config file (defaults to /etc/sendmail.toml)")
@ -157,7 +49,7 @@ func main() {
flag.BoolVar(&dbg, "dbg", false, "Enable debugging output")
flag.StringVar(&serverAddress, "server-address", "", "The SMTP server address")
flag.IntVar(&serverPort_, "server-port", 0, "The SMTP server")
flag.StringVar(&encryption, "encryption", "", "The encryption type (no, starttls, tls)")
flag.BoolVar(&encryption, "force-ssl", false, "Force the use of ssl (defalut: false)")
flag.StringVar(&user, "user", "", "The user to authenticate with to the server")
flag.StringVar(&password, "password", "", "The password to authenticate with to the server")
flag.StringVar(&to, "to", "", "Comma-separated list of recipient(s)")
@ -167,15 +59,26 @@ func main() {
flag.StringVar(&subject, "sub", "", "Subject of the mail")
flag.Parse()
LogInit(dbg)
if flag.NArg() == 0 {
text = readFromConsole()
} else {
for _, arg := range flag.Args() {
text += fmt.Sprintf("%s\n", arg)
}
}
serverPort = int64(serverPort_)
debug(
dbg,
`parameters:
Debug.F(
`
---
parameters:
conf: %s
dbg: %t
address: %s
port: %d
encryption: %s
encryption: %t
user: %s
password: %s
to: %s
@ -193,21 +96,27 @@ func main() {
subject,
)
config := readConfig(configPath, section)
debug(dbg, "\n%s", *config)
Debug.F("---\nConfig from %s\n%s", configPath, *config)
// config.Server.Merge("Address", serverAddress)
// config.Server.Merge("Port", serverPort)
// config.Server.Merge("Encryption", encryption)
// config.Server.Merge("User", user)
// config.Server.Merge("Password", password)
config.Merge("Server", &ServerConfig{serverAddress, serverPort, encryption, user, password})
config.Server.Merge("Address", serverAddress)
config.Server.Merge("Port", serverPort)
config.Server.Merge("Encryption", encryption)
config.Server.Merge("User", user)
config.Server.Merge("Password", password)
config.Merge("From", from)
config.Merge("To", strings.Split(to, ","))
config.Merge("Cc", strings.Split(cc, ","))
config.Merge("Bcc", strings.Split(bcc, ","))
config.Merge("To", toList(to))
config.Merge("Cc", toList(cc))
config.Merge("Bcc", toList(bcc))
config.Merge("Subject", subject)
// config.Merge("Text", text)
debug(dbg, "\n%s", config)
config.Merge("Text", text)
Debug.F("---\nPre-validation config\n%s", config)
err = config.Validate()
if err != nil {
Error.F("Config validation failed:\n%s\n", err)
os.Exit(1)
}
Info.F("Sending mail | to: %s", config.To)
SendMail(config)
}

View file

@ -16,7 +16,7 @@
[default.server]
address = "1.3.1.2"
port = 587
encryption = "tls"
encryption = true
user = "faggiano@uccelli.net"
password = "yougetit"
@ -38,7 +38,7 @@
[personal.server]
address = "10.13.12.10"
port = 25
encryption = "starttls"
encryption = true
user = "master@domain.local"
password = "yougetitagain"
@ -46,5 +46,5 @@
[secret.server]
address = "my.server.org"
port = 12345
encryption = "tls"
encryption = false
user = "me"

53
utils.go Normal file
View file

@ -0,0 +1,53 @@
package main
import "reflect"
func IsPresent(slice []string, val string) bool {
for _, item := range slice {
if item == val {
return true
}
}
return false
}
func IsEmpty(value interface{}) bool {
t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Slice:
return v.Len() == 0
default:
return value == reflect.Zero(t).Interface()
}
}
func Merge(key string, value, obj interface{}) {
if IsEmpty(value) {
return
}
field := reflect.ValueOf(obj).Elem().FieldByName(key)
if field.CanSet() {
switch field.Kind() {
case reflect.Int:
field.Set(reflect.ValueOf(value))
case reflect.Int16:
field.Set(reflect.ValueOf(value))
case reflect.Int32:
field.Set(reflect.ValueOf(value))
case reflect.Int64:
field.SetInt(value.(int64))
case reflect.Bool:
field.SetBool(value.(bool))
case reflect.String:
field.SetString(value.(string))
case reflect.Slice:
field.Set(reflect.ValueOf(value))
case reflect.Ptr:
field.Set(reflect.ValueOf(value))
default:
Merge(key, value, field)
}
}
}