package main import ( "bufio" "bytes" "encoding/base64" "flag" "fmt" "io" "net/smtp" "os" "regexp" "strings" "github.com/mattermost/rsc/google" ) func enc(s string) string { // TODO =? .. ?= return s } type Addr struct { Name string Email string } func (a Addr) enc() string { if a.Name == "" { return "<" + a.Email + ">" } if a.Email == "" { return enc(a.Name) + ":;" } return enc(a.Name) + " <" + a.Email + ">" } type Addrs []Addr func (a *Addrs) String() string { return "[addrlist]" } func (a Addrs) has(s string) bool { for _, aa := range a { if aa.Email == s { return true } } return false } func (a *Addrs) Set(s string) bool { s = strings.TrimSpace(s) if strings.HasSuffix(s, ">") { j := strings.LastIndex(s, "<") if j >= 0 { *a = append(*a, Addr{strings.TrimSpace(s[:j]), s[j+1 : len(s)-1]}) return true } } if strings.Contains(s, " ") { fmt.Fprintf(os.Stderr, "invalid address: %s", s) os.Exit(2) } *a = append(*a, Addr{"", s}) return true } func (a *Addrs) parseLine(s string) { for _, f := range strings.Split(s, ",") { f = strings.TrimSpace(f) if f != "" { a.Set(f) } } } func (a Addrs) fixDomain() { i := strings.Index(acct.Email, "@") if i < 0 { return } dom := acct.Email[i:] for i := range a { if a[i].Email != "" && !strings.Contains(a[i].Email, "@") { a[i].Email += dom } } } var from, to, cc, bcc, replyTo Addrs var inReplyTo, subject string var appendFile = flag.String("append", "", "file to append to end of body") var acct google.Account var acctName = flag.String("a", "", "account to use") var inputHeader = flag.Bool("i", false, "read additional header lines from stdin") func holdmode() { if os.Getenv("TERM") == "9term" { // forgive me os.Stdout.WriteString("\x1B];*9term-hold+\x07") } } func match(line, prefix string, arg *string) bool { if len(line) < len(prefix) || !strings.EqualFold(line[:len(prefix)], prefix) { return false } *arg = strings.TrimSpace(line[len(prefix):]) return true } func main() { flag.StringVar(&inReplyTo, "in-reply-to", "", "In-Reply-To") flag.StringVar(&subject, "s", "", "Subject") flag.Var(&from, "from", "From (can repeat)") flag.Var(&to, "to", "To (can repeat)") flag.Var(&cc, "cc", "CC (can repeat)") flag.Var(&bcc, "bcc", "BCC (can repeat)") flag.Var(&replyTo, "replyTo", "Reply-To (can repeat)") flag.Parse() if flag.NArg() != 0 && !*inputHeader { flag.Usage() } var body bytes.Buffer input := bufio.NewReader(os.Stdin) if *inputHeader { holdmode() Loop: for { s, err := input.ReadString('\n') if err != nil { if err == io.EOF { break Loop } fmt.Fprintf(os.Stderr, "reading stdin: %s\n", err) os.Exit(2) } var arg string switch { default: if ok, _ := regexp.MatchString(`^\S+:`, s); ok { fmt.Fprintf(os.Stderr, "unknown header line: %s", s) os.Exit(2) } body.WriteString(s) break Loop case match(s, "from:", &arg): from.parseLine(arg) case match(s, "to:", &arg): to.parseLine(arg) case match(s, "cc:", &arg): cc.parseLine(arg) case match(s, "bcc:", &arg): bcc.parseLine(arg) case match(s, "reply-to:", &arg): replyTo.parseLine(arg) case match(s, "subject:", &arg): subject = arg case match(s, "in-reply-to:", &arg): inReplyTo = arg } } } acct = google.Acct(*acctName) from.fixDomain() to.fixDomain() cc.fixDomain() bcc.fixDomain() replyTo.fixDomain() smtpTo := append(append(to, cc...), bcc...) if len(from) == 0 { // TODO: Much better name := "" email := acct.Email if email == "rsc@swtch.com" || email == "rsc@google.com" { name = "Russ Cox" } if email == "rsc@google.com" && (smtpTo.has("go@googlecode.com") || smtpTo.has("golang-dev@googlegroups.com") || smtpTo.has("golang-nuts@googlegroups.com")) { from = append(from, Addr{name, "rsc@golang.org"}) } else { from = append(from, Addr{name, email}) } } if len(from) > 1 { fmt.Fprintf(os.Stderr, "missing -from\n") os.Exit(2) } if len(to)+len(cc)+len(bcc) == 0 { fmt.Fprintf(os.Stderr, "missing destinations\n") os.Exit(2) } if !*inputHeader { holdmode() } _, err := io.Copy(&body, input) if err != nil { fmt.Fprintf(os.Stderr, "reading stdin: %s\n", err) os.Exit(2) } if *appendFile != "" { f, err := os.Open(*appendFile) if err != nil { fmt.Fprintf(os.Stderr, "append: %s\n", err) os.Exit(2) } _, err = io.Copy(&body, f) f.Close() if err != nil { fmt.Fprintf(os.Stderr, "append: %s\n", err) os.Exit(2) } } var msg bytes.Buffer fmt.Fprintf(&msg, "MIME-Version: 1.0\n") if len(from) > 0 { fmt.Fprintf(&msg, "From: ") for i, a := range from { if i > 0 { fmt.Fprintf(&msg, ", ") } fmt.Fprintf(&msg, "%s", a.enc()) } fmt.Fprintf(&msg, "\n") } if len(to) > 0 { fmt.Fprintf(&msg, "To: ") for i, a := range to { if i > 0 { fmt.Fprintf(&msg, ", ") } fmt.Fprintf(&msg, "%s", a.enc()) } fmt.Fprintf(&msg, "\n") } if len(cc) > 0 { fmt.Fprintf(&msg, "CC: ") for i, a := range cc { if i > 0 { fmt.Fprintf(&msg, ", ") } fmt.Fprintf(&msg, "%s", a.enc()) } fmt.Fprintf(&msg, "\n") } if len(replyTo) > 0 { fmt.Fprintf(&msg, "Reply-To: ") for i, a := range replyTo { if i > 0 { fmt.Fprintf(&msg, ", ") } fmt.Fprintf(&msg, "%s", a.enc()) } fmt.Fprintf(&msg, "\n") } if inReplyTo != "" { fmt.Fprintf(&msg, "In-Reply-To: %s\n", inReplyTo) } if subject != "" { fmt.Fprintf(&msg, "Subject: %s\n", enc(subject)) } fmt.Fprintf(&msg, "Date: xxx\n") fmt.Fprintf(&msg, "Content-Type: text/plain; charset=\"utf-8\"\n") fmt.Fprintf(&msg, "Content-Transfer-Encoding: base64\n") fmt.Fprintf(&msg, "\n") enc64 := base64.StdEncoding.EncodeToString(body.Bytes()) for len(enc64) > 72 { fmt.Fprintf(&msg, "%s\n", enc64[:72]) enc64 = enc64[72:] } fmt.Fprintf(&msg, "%s\n\n", enc64) auth := smtp.PlainAuth( "", acct.Email, acct.Password, "smtp.gmail.com", ) var smtpToEmail []string for _, a := range smtpTo { if a.Email != "" { smtpToEmail = append(smtpToEmail, a.Email) } } if err := sendMail("smtp.gmail.com:587", auth, from[0].Email, smtpToEmail, msg.Bytes()); err != nil { fmt.Fprintf(os.Stderr, "sending mail: %s\n", err) os.Exit(2) } } /* MIME-Version: 1.0 Subject: commit/plan9port: rsc: 9term: hold mode back door From: Bitbucket To: plan9port-dev@googlegroups.com Date: Tue, 11 Oct 2011 13:34:30 -0000 Message-ID: <20111011133430.31146.55070@bitbucket13.managed.contegix.com> Reply-To: commits-noreply@bitbucket.org Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable 1 new changeset in plan9port: http://bitbucket.org/rsc/plan9port/changeset/8735d7708a1b/ changeset: 8735d7708a1b user: rsc date: 2011-10-11 15:34:25 summary: 9term: hold mode back door R=3Drsc http://codereview.appspot.com/5248056 affected #: 2 files (-1 bytes) Repository URL: https://bitbucket.org/rsc/plan9port/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. */ func sendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) error { c, err := smtp.Dial(addr) if err != nil { return err } if err = c.StartTLS(nil); err != nil { return err } if err = c.Auth(a); err != nil { return err } if err = c.Mail(from); err != nil { return err } for _, addr := range to { if err = c.Rcpt(addr); err != nil { return err } } w, err := c.Data() if err != nil { return err } _, err = w.Write(msg) if err != nil { return err } err = w.Close() if err != nil { return err } return c.Quit() }