diff --git a/boardvoting.go b/boardvoting.go index e8a83e4..7c957d7 100644 --- a/boardvoting.go +++ b/boardvoting.go @@ -23,7 +23,7 @@ import ( "github.com/gorilla/csrf" "github.com/gorilla/sessions" _ "github.com/mattn/go-sqlite3" - "github.com/op/go-logging" + log "github.com/sirupsen/logrus" "gopkg.in/yaml.v2" "git.cacert.org/cacert-boardvoting/boardvoting" @@ -35,7 +35,6 @@ var store *sessions.CookieStore var csrfKey []byte var version = "undefined" var build = "undefined" -var log *logging.Logger const sessionCookieName = "votesession" @@ -74,7 +73,7 @@ func renderTemplate(w http.ResponseWriter, r *http.Request, templates []string, type contextKey int const ( - ctxNeedsAuth contextKey = iota + ctxNeedsAuth contextKey = iota ctxVoter ctxDecision ctxVote @@ -128,7 +127,7 @@ type motionParameters struct { } type motionListParameters struct { - Page int64 + Page int64 Flags struct { Confirmed, Withdraw, Unvoted bool } @@ -178,7 +177,11 @@ func motionListHandler(w http.ResponseWriter, r *http.Request) { if flashes := session.Flashes(); len(flashes) > 0 { templateContext.Flashes = flashes } - session.Save(r, w) + err = session.Save(r, w) + if err != nil { + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } templateContext.Params = ¶ms if templateContext.Decisions, err = FindDecisionsForDisplayOnPage(params.Page, params.Flags.Unvoted, templateContext.Voter); err != nil { @@ -281,16 +284,15 @@ type FlashMessageAction struct{} func (a *FlashMessageAction) AddFlash(w http.ResponseWriter, r *http.Request, message interface{}, tags ...string) { session, err := store.Get(r, sessionCookieName) if err != nil { - log.Errorf("getting session failed: %v", err) + log.Warnf("could not get session cookie: %v", err) return } session.AddFlash(message, tags...) - session.Save(r, w) + err = session.Save(r, w) if err != nil { - log.Errorf("saving session failed: %v", err) + log.Warnf("could not save flash message: %v", err) return } - return } type withDrawMotionAction struct { @@ -530,7 +532,7 @@ func (h *directVoteHandler) Handle(w http.ResponseWriter, r *http.Request) { case http.MethodPost: voteResult := &Vote{ VoterId: voter.Id, Vote: vote, DecisionId: decision.Id, Voted: time.Now().UTC(), - Notes: fmt.Sprintf("Direct Vote\n\n%s", getPEMClientCert(r))} + Notes: fmt.Sprintf("Direct Vote\n\n%s", getPEMClientCert(r))} if err := voteResult.Save(); err != nil { log.Errorf("Problem saving vote: %v", err) http.Error(w, "Problem saving vote", http.StatusInternalServerError) @@ -566,7 +568,10 @@ type proxyVoteHandler struct { func getPEMClientCert(r *http.Request) string { clientCertPEM := bytes.NewBufferString("") authenticatedCertificate := r.Context().Value(ctxAuthenticatedCert).(*x509.Certificate) - pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: authenticatedCertificate.Raw}) + err := pem.Encode(clientCertPEM, &pem.Block{Type: "CERTIFICATE", Bytes: authenticatedCertificate.Raw}) + if err != nil { + log.Errorf("error encoding client certificate: %v", err) + } return clientCertPEM.String() } @@ -692,42 +697,12 @@ type Config struct { MigrationsPath string `yaml:"migrations_path"` HttpAddress string `yaml:"http_address"` HttpsAddress string `yaml:"https_address"` - MailServer struct { + MailServer struct { Host string `yaml:"host"` Port int `yaml:"port"` } `yaml:"mail_server"` } -func setupLogging(ctx context.Context) { - log = logging.MustGetLogger("boardvoting") - consoleLogFormat := logging.MustStringFormatter(`%{color}%{time:20060102 15:04:05.000-0700} %{longfile} ▶ %{level:s} %{id:05d}%{color:reset} %{message}`) - fileLogFormat := logging.MustStringFormatter(`%{time:20060102 15:04:05.000-0700} %{level:s} %{id:05d} %{message}`) - - consoleBackend := logging.NewLogBackend(os.Stderr, "", 0) - - logfile, err := os.OpenFile("boardvoting.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.FileMode(0640)) - if err != nil { - panic("Could not open logfile") - } - - fileBackend := logging.NewLogBackend(logfile, "", 0) - fileBackendLeveled := logging.AddModuleLevel(logging.NewBackendFormatter(fileBackend, fileLogFormat)) - fileBackendLeveled.SetLevel(logging.INFO, "") - - logging.SetBackend(fileBackendLeveled, - logging.NewBackendFormatter(consoleBackend, consoleLogFormat)) - - go func() { - for range ctx.Done() { - if err = logfile.Close(); err != nil { - fmt.Fprintf(os.Stderr, "Problem closing the log file: %v", err) - } - } - }() - - log.Info("Setup logging") -} - func readConfig() { source, err := ioutil.ReadFile(configFile) if err != nil { @@ -773,7 +748,7 @@ func setupDbConfig(ctx context.Context) { go func() { for range ctx.Done() { if err := db.Close(); err != nil { - fmt.Fprintf(os.Stderr, "Problem closing the database: %v", err) + _, _ = fmt.Fprintf(os.Stderr, "Problem closing the database: %v", err) } } }() @@ -838,11 +813,12 @@ func init() { } func main() { + log.Infof("CAcert Board Voting version %s, build %s", version, build) + flag.Parse() var stopAll func() executionContext, stopAll := context.WithCancel(context.Background()) - setupLogging(executionContext) readConfig() setupDbConfig(executionContext) setupNotifications(executionContext) @@ -852,8 +828,6 @@ func main() { defer stopAll() - log.Infof("CAcert Board Voting version %s, build %s", version, build) - server := &http.Server{ Addr: config.HttpsAddress, TLSConfig: tlsConfig, diff --git a/go.mod b/go.mod index 1ebf8e7..b637072 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/imdario/mergo v0.3.7 // indirect github.com/jmoiron/sqlx v1.2.0 github.com/mattn/go-sqlite3 v1.11.0 - github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079 + github.com/sirupsen/logrus v1.4.2 github.com/ziutek/mymysql v1.5.4 // indirect golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect google.golang.org/appengine v1.6.1 // indirect diff --git a/go.sum b/go.sum index bf2802f..caf79c2 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -59,8 +60,6 @@ github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -72,6 +71,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079 h1:xPeaaIHjF9j8jbYQ5xdvLnFp+lpmGYFG1uBPtXNBHno= github.com/rubenv/sql-migrate v0.0.0-20190717103323-87ce952f7079/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -104,6 +104,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c h1:+EXw7AwNOKzPFXMZ1yNjO40aWCh3PIquJB2fYlv9wcs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/jobs.go b/jobs.go index 46ed7dc..0fe6086 100644 --- a/jobs.go +++ b/jobs.go @@ -1,6 +1,9 @@ package main -import "time" +import ( + log "github.com/sirupsen/logrus" + "time" +) type Job interface { Schedule() diff --git a/models.go b/models.go index 8c2f968..a2740ad 100644 --- a/models.go +++ b/models.go @@ -3,10 +3,12 @@ package main import ( "database/sql" "fmt" - dbmig "git.cacert.org/cacert-boardvoting/db" "github.com/jmoiron/sqlx" "github.com/rubenv/sql-migrate" "time" + + migrations "git.cacert.org/cacert-boardvoting/db" + log "github.com/sirupsen/logrus" ) type sqlKey int @@ -250,16 +252,16 @@ func NewDB(database *sql.DB) *dbHandler { var stmt *sqlx.Stmt stmt, err := handler.db.Preparex(sqlStatement) if err != nil { - log.Critical("ERROR parsing statement %s: %s", sqlStatement, err) + log.Errorf("error parsing statement %s: %s", sqlStatement, err) failedStatements = append(failedStatements, sqlStatement) } - stmt.Close() + _ = stmt.Close() } if len(failedStatements) > 0 { log.Panicf("%d statements failed to prepare", len(failedStatements)) } - _, err := migrate.Exec(database, "sqlite3", dbmig.Migrations(), migrate.Up) + _, err := migrate.Exec(database, "sqlite3", migrations.Migrations(), migrate.Up) if err != nil { log.Panicf("running database migration failed: %v", err) } @@ -288,7 +290,7 @@ func (d *dbHandler) getPreparedStatement(statementKey sqlKey) *sqlx.Stmt { func (v *Vote) Save() (err error) { insertVoteStmt := db.getPreparedNamedStatement(sqlCreateVote) - defer insertVoteStmt.Close() + defer func() { _ = insertVoteStmt.Close() }() if _, err = insertVoteStmt.Exec(v); err != nil { log.Errorf("saving vote failed: %v", err) @@ -296,7 +298,7 @@ func (v *Vote) Save() (err error) { } getVoteStmt := db.getPreparedStatement(sqlLoadVote) - defer getVoteStmt.Close() + defer func() { _ = getVoteStmt.Close() }() if err = getVoteStmt.Get(v, v.DecisionId, v.VoterId); err != nil { log.Errorf("getting inserted vote failed: %v", err) @@ -353,13 +355,14 @@ type DecisionForDisplay struct { func FindDecisionForDisplayByTag(tag string) (decision *DecisionForDisplay, err error) { decisionStmt := db.getPreparedStatement(sqlLoadDecisionByTag) - defer decisionStmt.Close() + defer func() { _ = decisionStmt.Close() }() decision = &DecisionForDisplay{} if err = decisionStmt.Get(decision, tag); err != nil { if err == sql.ErrNoRows { decision = nil err = nil + return } else { log.Errorf("getting motion %s failed: %v", tag, err) return @@ -381,7 +384,7 @@ func FindDecisionsForDisplayOnPage(page int64, unvoted bool, voter *Voter) (deci } else { decisionsStmt = db.getPreparedStatement(sqlLoadDecisions) } - defer decisionsStmt.Close() + defer func() { _ = decisionsStmt.Close() }() var rows *sqlx.Rows if unvoted && voter != nil { @@ -393,7 +396,7 @@ func FindDecisionsForDisplayOnPage(page int64, unvoted bool, voter *Voter) (deci log.Errorf("loading motions for page %d failed: %v", page, err) return } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var d DecisionForDisplay @@ -412,14 +415,14 @@ func FindDecisionsForDisplayOnPage(page int64, unvoted bool, voter *Voter) (deci func (d *Decision) VoteSums() (sums *VoteSums, err error) { votesStmt := db.getPreparedStatement(sqlLoadVoteCountsForDecision) - defer votesStmt.Close() + defer func() { _ = votesStmt.Close() }() voteRows, err := votesStmt.Queryx(d.Id) if err != nil { log.Errorf("fetching vote sums for motion %s failed: %v", d.Tag, err) return } - defer voteRows.Close() + defer func() { _ = voteRows.Close() }() sums = &VoteSums{} for voteRows.Next() { @@ -443,7 +446,7 @@ func (d *Decision) VoteSums() (sums *VoteSums, err error) { func (d *DecisionForDisplay) LoadVotes() (err error) { votesStmt := db.getPreparedStatement(sqlLoadVotesForDecision) - defer votesStmt.Close() + defer func() { _ = votesStmt.Close() }() err = votesStmt.Select(&d.Votes, d.Id) if err != nil { @@ -456,7 +459,7 @@ func (d *DecisionForDisplay) LoadVotes() (err error) { func (d *Decision) OlderExists(unvoted bool, voter *Voter) (result bool, err error) { if unvoted && voter != nil { olderStmt := db.getPreparedStatement(sqlCountOlderThanUnvotedDecision) - defer olderStmt.Close() + defer func() { _ = olderStmt.Close() }() if err = olderStmt.Get(&result, d.Proposed, voter.Id); err != nil { log.Errorf("finding older motions than %s failed: %v", d.Tag, err) @@ -464,7 +467,7 @@ func (d *Decision) OlderExists(unvoted bool, voter *Voter) (result bool, err err } } else { olderStmt := db.getPreparedStatement(sqlCountOlderThanDecision) - defer olderStmt.Close() + defer func() { _ = olderStmt.Close() }() if err = olderStmt.Get(&result, d.Proposed); err != nil { log.Errorf("finding older motions than %s failed: %v", d.Tag, err) @@ -477,7 +480,7 @@ func (d *Decision) OlderExists(unvoted bool, voter *Voter) (result bool, err err func (d *Decision) Create() (err error) { insertDecisionStmt := db.getPreparedNamedStatement(sqlCreateDecision) - defer insertDecisionStmt.Close() + defer func() { _ = insertDecisionStmt.Close() }() result, err := insertDecisionStmt.Exec(d) if err != nil { @@ -493,7 +496,7 @@ func (d *Decision) Create() (err error) { rescheduleChannel <- JobIdCloseDecisions getDecisionStmt := db.getPreparedStatement(sqlLoadDecisionById) - defer getDecisionStmt.Close() + defer func() { _ = getDecisionStmt.Close() }() err = getDecisionStmt.Get(d, lastInsertId) if err != nil { @@ -506,7 +509,7 @@ func (d *Decision) Create() (err error) { func (d *Decision) LoadWithId() (err error) { getDecisionStmt := db.getPreparedStatement(sqlLoadDecisionById) - defer getDecisionStmt.Close() + defer func() { _ = getDecisionStmt.Close() }() err = getDecisionStmt.Get(d, d.Id) if err != nil { @@ -519,7 +522,7 @@ func (d *Decision) LoadWithId() (err error) { func (d *Decision) Update() (err error) { updateDecisionStmt := db.getPreparedNamedStatement(sqlUpdateDecision) - defer updateDecisionStmt.Close() + defer func() { _ = updateDecisionStmt.Close() }() result, err := updateDecisionStmt.Exec(d) if err != nil { @@ -541,7 +544,7 @@ func (d *Decision) Update() (err error) { func (d *Decision) UpdateStatus() (err error) { updateStatusStmt := db.getPreparedNamedStatement(sqlUpdateDecisionStatus) - defer updateStatusStmt.Close() + defer func() { _ = updateStatusStmt.Close() }() result, err := updateStatusStmt.Exec(d) if err != nil { @@ -567,7 +570,7 @@ func (d *Decision) String() string { func FindVoterByAddress(emailAddress string) (voter *Voter, err error) { findVoterStmt := db.getPreparedStatement(sqlLoadEnabledVoterByEmail) - defer findVoterStmt.Close() + defer func() { _ = findVoterStmt.Close() }() voter = &Voter{} if err = findVoterStmt.Get(voter, emailAddress); err != nil { @@ -595,7 +598,7 @@ func (d *Decision) Close() error { d.Status, reasoning = voteSums.CalculateResult(quorum, majority) closeDecisionStmt := db.getPreparedNamedStatement(sqlUpdateDecisionStatus) - defer closeDecisionStmt.Close() + defer func() { _ = closeDecisionStmt.Close() }() result, err := closeDecisionStmt.Exec(d) if err != nil { @@ -618,7 +621,7 @@ func (d *Decision) Close() error { func CloseDecisions() (err error) { getClosableDecisionsStmt := db.getPreparedNamedStatement(sqlSelectClosableDecisions) - defer getClosableDecisionsStmt.Close() + defer func() { _ = getClosableDecisionsStmt.Close() }() decisions := make([]*Decision, 0) rows, err := getClosableDecisionsStmt.Queryx(struct{ Now time.Time }{time.Now().UTC()}) @@ -626,7 +629,7 @@ func CloseDecisions() (err error) { log.Errorf("fetching closable decisions failed: %v", err) return } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { decision := &Decision{} if err = rows.StructScan(decision); err != nil { @@ -635,10 +638,10 @@ func CloseDecisions() (err error) { } decisions = append(decisions, decision) } - rows.Close() + defer func() { _ = rows.Close() }() for _, decision := range decisions { - log.Debugf("found closable decision %s", decision.Tag) + log.Infof("found closable decision %s", decision.Tag) if err = decision.Close(); err != nil { log.Errorf("closing decision %s failed: %s", decision.Tag, err) return @@ -650,7 +653,7 @@ func CloseDecisions() (err error) { func GetNextPendingDecisionDue() (due *time.Time, err error) { getNextPendingDecisionDueStmt := db.getPreparedStatement(sqlGetNextPendingDecisionDue) - defer getNextPendingDecisionDueStmt.Close() + defer func() { _ = getNextPendingDecisionDueStmt.Close() }() row := getNextPendingDecisionDueStmt.QueryRow() @@ -669,7 +672,7 @@ func GetNextPendingDecisionDue() (due *time.Time, err error) { func GetReminderVoters() (voters *[]Voter, err error) { getReminderVotersStmt := db.getPreparedStatement(sqlGetReminderVoters) - defer getReminderVotersStmt.Close() + defer func() { _ = getReminderVotersStmt.Close() }() voterSlice := make([]Voter, 0) @@ -684,7 +687,7 @@ func GetReminderVoters() (voters *[]Voter, err error) { func FindUnvotedDecisionsForVoter(voter *Voter) (decisions *[]Decision, err error) { findUnvotedDecisionsForVoterStmt := db.getPreparedStatement(sqlFindUnvotedDecisionsForVoter) - defer findUnvotedDecisionsForVoterStmt.Close() + defer func() { _ = findUnvotedDecisionsForVoterStmt.Close() }() decisionsSlice := make([]Decision, 0) @@ -699,7 +702,7 @@ func FindUnvotedDecisionsForVoter(voter *Voter) (decisions *[]Decision, err erro func GetVoterById(id int64) (voter *Voter, err error) { getVoterByIdStmt := db.getPreparedStatement(sqlGetEnabledVoterById) - defer getVoterByIdStmt.Close() + defer func() { _ = getVoterByIdStmt.Close() }() voter = &Voter{} if err = getVoterByIdStmt.Get(voter, id); err != nil { @@ -712,7 +715,7 @@ func GetVoterById(id int64) (voter *Voter, err error) { func GetVotersForProxy(proxy *Voter) (voters *[]Voter, err error) { getVotersForProxyStmt := db.getPreparedStatement(sqlGetVotersForProxy) - defer getVotersForProxyStmt.Close() + defer func() { _ = getVotersForProxyStmt.Close() }() votersSlice := make([]Voter, 0) diff --git a/notifications.go b/notifications.go index 25234ff..28c6a21 100644 --- a/notifications.go +++ b/notifications.go @@ -7,6 +7,8 @@ import ( "github.com/Masterminds/sprig" "gopkg.in/gomail.v2" "text/template" + + log "github.com/sirupsen/logrus" ) type headerData struct { @@ -57,7 +59,7 @@ func MailNotifier(quitMailNotifier chan int) { } m.SetBody("text/plain", mailText.String()) - d := gomail.NewPlainDialer(config.MailServer.Host, config.MailServer.Port, "", "") + d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "") if err := d.DialAndSend(m); err != nil { log.Errorf("sending mail failed: %v", err) }