package main import ( "bytes" "fmt" "github.com/Masterminds/sprig" "gopkg.in/gomail.v2" "text/template" ) type headerData struct { name string value []string } type headerList []headerData type recipientData struct { field, address, name string } type notificationContent struct { template string data interface{} subject string headers headerList recipients []recipientData } type NotificationMail interface { GetNotificationContent() *notificationContent } var NotifyMailChannel = make(chan NotificationMail, 1) func MailNotifier(quitMailNotifier chan int) { log.Info("Launched mail notifier") for { select { case notification := <-NotifyMailChannel: content := notification.GetNotificationContent() mailText, err := buildMail(content.template, content.data) if err != nil { log.Errorf("building mail failed: %v", err) continue } m := gomail.NewMessage() m.SetAddressHeader("From", config.NotificationSenderAddress, "CAcert board voting system") for _, recipient := range content.recipients { m.SetAddressHeader(recipient.field, recipient.address, recipient.name) } m.SetHeader("Subject", content.subject) for _, header := range content.headers { m.SetHeader(header.name, header.value...) } m.SetBody("text/plain", mailText.String()) d := gomail.NewDialer(config.MailServer.Host, config.MailServer.Port, "", "") if err := d.DialAndSend(m); err != nil { log.Errorf("sending mail failed: %v", err) } case <-quitMailNotifier: log.Info("Ending mail notifier") return } } } func buildMail(templateName string, context interface{}) (mailText *bytes.Buffer, err error) { t, err := template.New(templateName).Funcs( sprig.GenericFuncMap()).ParseFiles(fmt.Sprintf("templates/%s", templateName)) if err != nil { return } mailText = bytes.NewBufferString("") if err := t.Execute(mailText, context); err != nil { log.Errorf("Failed to execute template %s with context %+v: %v", templateName, context, err) return nil, err } return } type notificationBase struct{} func (n *notificationBase) getRecipient() recipientData { return recipientData{field: "To", address: config.NoticeMailAddress, name: "CAcert board mailing list"} } type decisionReplyBase struct { decision Decision } func (n *decisionReplyBase) getHeaders() headerList { headers := make(headerList, 0) headers = append(headers, headerData{ name: "References", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)}, }) headers = append(headers, headerData{ name: "In-Reply-To", value: []string{fmt.Sprintf("<%s>", n.decision.Tag)}, }) return headers } func (n *decisionReplyBase) getSubject() string { return fmt.Sprintf("Re: %s - %s", n.decision.Tag, n.decision.Title) } type notificationClosedDecision struct { notificationBase decisionReplyBase voteSums VoteSums reasoning string } func NewNotificationClosedDecision(decision *Decision, voteSums *VoteSums, reasoning string) NotificationMail { notification := ¬ificationClosedDecision{voteSums: *voteSums, reasoning: reasoning} notification.decision = *decision return notification } func (n *notificationClosedDecision) GetNotificationContent() *notificationContent { return ¬ificationContent{ template: "closed_motion_mail.txt", data: struct { *Decision *VoteSums Reasoning string }{&n.decision, &n.voteSums, n.reasoning}, subject: fmt.Sprintf("Re: %s - %s - finalised", n.decision.Tag, n.decision.Title), headers: n.decisionReplyBase.getHeaders(), recipients: []recipientData{n.notificationBase.getRecipient()}, } } type NotificationCreateMotion struct { notificationBase decision Decision voter Voter } func (n *NotificationCreateMotion) GetNotificationContent() *notificationContent { voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag) unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL) return ¬ificationContent{ template: "create_motion_mail.txt", data: struct { *Decision Name string VoteURL string UnvotedURL string }{&n.decision, n.voter.Name, voteURL, unvotedURL}, subject: fmt.Sprintf("%s - %s", n.decision.Tag, n.decision.Title), headers: headerList{headerData{"Message-ID", []string{fmt.Sprintf("<%s>", n.decision.Tag)}}}, recipients: []recipientData{n.notificationBase.getRecipient()}, } } type notificationUpdateMotion struct { notificationBase decisionReplyBase voter Voter } func NewNotificationUpdateMotion(decision Decision, voter Voter) NotificationMail { notification := notificationUpdateMotion{voter: voter} notification.decision = decision return ¬ification } func (n *notificationUpdateMotion) GetNotificationContent() *notificationContent { voteURL := fmt.Sprintf("%s/vote/%s", config.BaseURL, n.decision.Tag) unvotedURL := fmt.Sprintf("%s/motions/?unvoted=1", config.BaseURL) return ¬ificationContent{ template: "update_motion_mail.txt", data: struct { *Decision Name string VoteURL string UnvotedURL string }{&n.decision, n.voter.Name, voteURL, unvotedURL}, subject: n.decisionReplyBase.getSubject(), headers: n.decisionReplyBase.getHeaders(), recipients: []recipientData{n.notificationBase.getRecipient()}, } } type notificationWithDrawMotion struct { notificationBase decisionReplyBase voter Voter } func NewNotificationWithDrawMotion(decision *Decision, voter *Voter) NotificationMail { notification := ¬ificationWithDrawMotion{voter: *voter} notification.decision = *decision return notification } func (n *notificationWithDrawMotion) GetNotificationContent() *notificationContent { return ¬ificationContent{ template: "withdraw_motion_mail.txt", data: struct { *Decision Name string }{&n.decision, n.voter.Name}, subject: fmt.Sprintf("Re: %s - %s - withdrawn", n.decision.Tag, n.decision.Title), headers: n.decisionReplyBase.getHeaders(), recipients: []recipientData{n.notificationBase.getRecipient()}, } } type RemindVoterNotification struct { voter Voter decisions []Decision } func (n *RemindVoterNotification) GetNotificationContent() *notificationContent { return ¬ificationContent{ template: "remind_voter_mail.txt", data: struct { Decisions []Decision Name string BaseURL string }{n.decisions, n.voter.Name, config.BaseURL}, subject: "Outstanding CAcert board votes", recipients: []recipientData{{"To", n.voter.Reminder, n.voter.Name}}, } } type voteNotificationBase struct{} func (n *voteNotificationBase) getRecipient() recipientData { return recipientData{"To", config.VoteNoticeMailAddress, "CAcert board votes mailing list"} } type notificationProxyVote struct { voteNotificationBase decisionReplyBase proxy Voter voter Voter vote Vote justification string } func NewNotificationProxyVote(decision *Decision, proxy *Voter, voter *Voter, vote *Vote, justification string) NotificationMail { notification := ¬ificationProxyVote{proxy: *proxy, voter: *voter, vote: *vote, justification: justification} notification.decision = *decision return notification } func (n *notificationProxyVote) GetNotificationContent() *notificationContent { return ¬ificationContent{ template: "proxy_vote_mail.txt", data: struct { Proxy string Vote VoteChoice Voter string Decision *Decision Justification string }{n.proxy.Name, n.vote.Vote, n.voter.Name, &n.decision, n.justification}, subject: n.decisionReplyBase.getSubject(), headers: n.decisionReplyBase.getHeaders(), recipients: []recipientData{n.voteNotificationBase.getRecipient()}, } } type notificationDirectVote struct { voteNotificationBase decisionReplyBase voter Voter vote Vote } func NewNotificationDirectVote(decision *Decision, voter *Voter, vote *Vote) NotificationMail { notification := ¬ificationDirectVote{voter: *voter, vote: *vote} notification.decision = *decision return notification } func (n *notificationDirectVote) GetNotificationContent() *notificationContent { return ¬ificationContent{ template: "direct_vote_mail.txt", data: struct { Vote VoteChoice Voter string Decision *Decision }{n.vote.Vote, n.voter.Name, &n.decision}, subject: n.decisionReplyBase.getSubject(), headers: n.decisionReplyBase.getHeaders(), recipients: []recipientData{n.voteNotificationBase.getRecipient()}, } }