From d95e8ce75cc193181fd8cf9272269fbfff911f66 Mon Sep 17 00:00:00 2001 From: Grégoire Duchêne Date: Sun, 4 Jul 2021 17:20:50 +0100 Subject: Simplify project structure --- archlinux/PKGBUILD | 28 +++++++++ cmd/fwdsms/config.go | 44 ------------- cmd/fwdsms/config_test.go | 36 ----------- cmd/fwdsms/mailer.go | 122 ------------------------------------- cmd/fwdsms/mailer_test.go | 52 ---------------- cmd/fwdsms/main.go | 89 --------------------------- config.go | 44 +++++++++++++ config_example.yaml | 20 ++++++ config_test.go | 36 +++++++++++ configs/example.yaml | 20 ------ deployments/archlinux/PKGBUILD | 28 --------- deployments/docker/Dockerfile | 13 ---- deployments/systemd/fwdsms.service | 13 ---- docker/Dockerfile | 14 +++++ mailer.go | 122 +++++++++++++++++++++++++++++++++++++ mailer_test.go | 52 ++++++++++++++++ main.go | 89 +++++++++++++++++++++++++++ systemd/fwdsms.service | 13 ++++ 18 files changed, 418 insertions(+), 417 deletions(-) create mode 100644 archlinux/PKGBUILD delete mode 100644 cmd/fwdsms/config.go delete mode 100644 cmd/fwdsms/config_test.go delete mode 100644 cmd/fwdsms/mailer.go delete mode 100644 cmd/fwdsms/mailer_test.go delete mode 100644 cmd/fwdsms/main.go create mode 100644 config.go create mode 100644 config_example.yaml create mode 100644 config_test.go delete mode 100644 configs/example.yaml delete mode 100644 deployments/archlinux/PKGBUILD delete mode 100644 deployments/docker/Dockerfile delete mode 100644 deployments/systemd/fwdsms.service create mode 100644 docker/Dockerfile create mode 100644 mailer.go create mode 100644 mailer_test.go create mode 100644 main.go create mode 100644 systemd/fwdsms.service diff --git a/archlinux/PKGBUILD b/archlinux/PKGBUILD new file mode 100644 index 0000000..5e9489b --- /dev/null +++ b/archlinux/PKGBUILD @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +# SPDX-License-Identifier: ISC + +# Maintainer: Grégoire Duchêne + +pkgname=fwdsms +pkgver=0.2.1 +pkgrel=1 +arch=(x86_64) +url=https://github.com/gduchene/fwdsms +license=(custom:ISC) +backup=(etc/${pkgname}.yaml) +makedepends=(go) +source=(git://github.com/gduchene/fwdsms.git#tag=v${pkgver}) +sha256sums=(SKIP) + +build() { + cd ${pkgname} + go build +} + +package() { + cd ${pkgname} + install -Dm755 ${pkgname} ${pkgdir}/usr/bin/${pkgname} + install -Dm644 config_example.yaml ${pkgdir}/etc/${pkgname}.yaml + install -Dm644 systemd/${pkgname}.service ${pkgdir}/usr/lib/systemd/system/${pkgname}.service + install -Dm644 LICENSE ${pkgdir}/usr/share/licenses/${pkgname}/LICENSE +} diff --git a/cmd/fwdsms/config.go b/cmd/fwdsms/config.go deleted file mode 100644 index 64a4a51..0000000 --- a/cmd/fwdsms/config.go +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -// SPDX-License-Identifier: ISC - -package main - -import ( - "io" - - "gopkg.in/yaml.v3" -) - -type Config struct { - Message Message `yaml:"message"` - SMTP SMTP `yaml:"smtp"` - Twilio Twilio `yaml:"twilio"` -} - -type Message struct { - From string `yaml:"from"` - To string `yaml:"to"` - Subject string `yaml:"subject"` - Template string `yaml:"template"` -} - -type SMTP struct { - Address string `yaml:"hostname"` - Username string `yaml:"username"` - Password string `yaml:"password"` -} - -type Twilio struct { - Address string `yaml:"address"` - AuthToken string `yaml:"authToken"` - Endpoint string `yaml:"endpoint"` -} - -func loadConfig(r io.Reader) (*Config, error) { - dec := yaml.NewDecoder(r) - cfg := &Config{} - if err := dec.Decode(cfg); err != nil { - return nil, err - } - return cfg, nil -} diff --git a/cmd/fwdsms/config_test.go b/cmd/fwdsms/config_test.go deleted file mode 100644 index a303719..0000000 --- a/cmd/fwdsms/config_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -const cfg string = ` -message: - from: foo@example.com - to: bar@example.com - subject: New SMS From {{.From}} For {{.To}} - template: | - From: {{.From}} - To: {{.To}} - Date: {{.DateReceived.UTC}} - - {{.Message}} - -smtp: - hostname: example.com:465 - username: bar - password: some password - -twilio: - address: /run/fwdsms/socket - authToken: some token - endpoint: / -` - -func TestConfig(t *testing.T) { - _, err := loadConfig(strings.NewReader(cfg)) - assert.NoError(t, err) -} diff --git a/cmd/fwdsms/mailer.go b/cmd/fwdsms/mailer.go deleted file mode 100644 index cb687c9..0000000 --- a/cmd/fwdsms/mailer.go +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -// SPDX-License-Identifier: ISC - -package main - -import ( - "bytes" - "context" - "crypto/tls" - "fmt" - "log" - "net" - "net/smtp" - "text/template" - "time" - - "go.awhk.org/fwdsms/pkg/twilio" -) - -type email struct { - from, to string - body []byte -} - -type mailer struct { - auth smtp.Auth - hostname string - sms <-chan twilio.SMS - tmplFrom, tmplTo, tmplMsg *template.Template -} - -func (m *mailer) sendEmail(e email) error { - dialer := &net.Dialer{Timeout: time.Second} - conn, err := tls.DialWithDialer(dialer, "tcp", m.hostname, nil) - if err != nil { - return err - } - if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { - log.Printf("Failed to set the SMTP connection deadline: %s.", err) - } - h, _, _ := net.SplitHostPort(m.hostname) - c, err := smtp.NewClient(conn, h) - if err != nil { - conn.Close() - return err - } - defer c.Close() - if err := c.Auth(m.auth); err != nil { - return err - } - - if err := c.Mail(e.from); err != nil { - return err - } - if err := c.Rcpt(e.to); err != nil { - return err - } - w, err := c.Data() - if err != nil { - return err - } - if _, err := w.Write(e.body); err != nil { - return err - } - if err = w.Close(); err != nil { - return nil - } - return c.Quit() -} - -func (m *mailer) newEmail(sms twilio.SMS) email { - var from, to, msg bytes.Buffer - if err := m.tmplFrom.Execute(&from, sms); err != nil { - log.Printf("Failed to apply a template: %v.", err) - } - if err := m.tmplTo.Execute(&to, sms); err != nil { - log.Printf("Failed to apply a template: %v.", err) - } - if err := m.tmplMsg.Execute(&msg, sms); err != nil { - log.Printf("Failed to apply a template: %v.", err) - } - return email{from.String(), to.String(), msg.Bytes()} -} - -func (m *mailer) start(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - case sms := <-m.sms: - if err := m.sendEmail(m.newEmail(sms)); err != nil { - log.Printf("Failed to send email: %v.", err) - } - } - } -} - -func newMailer(cfg *Config, sms <-chan twilio.SMS) *mailer { - if cfg.Message.From == "" { - log.Fatal("Missing From field.") - } - if cfg.Message.To == "" { - log.Fatal("Missing To field.") - } - if cfg.Message.Subject == "" { - log.Fatal("Missing Subject field.") - } - if cfg.Message.Template == "" { - log.Fatal("Missing Template field.") - } - tmplMsg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s\r\n", - cfg.Message.From, cfg.Message.To, cfg.Message.Subject, cfg.Message.Template) - host, _, _ := net.SplitHostPort(cfg.SMTP.Address) - return &mailer{ - auth: smtp.PlainAuth("", cfg.SMTP.Username, cfg.SMTP.Password, host), - hostname: cfg.SMTP.Address, - sms: sms, - tmplFrom: template.Must(template.New("from").Parse(cfg.Message.From)), - tmplTo: template.Must(template.New("to").Parse(cfg.Message.To)), - tmplMsg: template.Must(template.New("message").Parse(tmplMsg)), - } -} diff --git a/cmd/fwdsms/mailer_test.go b/cmd/fwdsms/mailer_test.go deleted file mode 100644 index e1376c3..0000000 --- a/cmd/fwdsms/mailer_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -// SPDX-License-Identifier: ISC - -package main - -import ( - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "go.awhk.org/fwdsms/pkg/twilio" -) - -func TestMailer_newEmail(t *testing.T) { - m := newMailer(&Config{ - Message: Message{ - From: "fwdsms@example.com", - To: "sms{{.To}}@example.com", - Subject: "New SMS From {{.From}}", - Template: `From: {{.From}} - To: {{.To}} -Date: {{.DateReceived.UTC}} - -{{.Body}}`, - }}, nil) - // Reserved phone numbers, see Ofcom's website. - sms := twilio.SMS{ - DateReceived: time.Unix(0, 0), - From: "+442079460123", - To: "+447700900123", - Body: "Hello World!", - } - wants := email{ - from: "fwdsms@example.com", - to: "sms+447700900123@example.com", - body: []byte(strings.Join([]string{ - "From: fwdsms@example.com", - "To: sms+447700900123@example.com", - "Subject: New SMS From +442079460123", - "", - `From: +442079460123 - To: +447700900123 -Date: 1970-01-01 00:00:00 +0000 UTC - -Hello World!`, - "", - }, "\r\n")), - } - assert.Equal(t, wants, m.newEmail(sms)) -} diff --git a/cmd/fwdsms/main.go b/cmd/fwdsms/main.go deleted file mode 100644 index 090331f..0000000 --- a/cmd/fwdsms/main.go +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -// SPDX-License-Identifier: ISC - -package main - -import ( - "context" - "flag" - "log" - "net" - "net/http" - "os" - "os/signal" - "time" - - "github.com/gorilla/handlers" - "github.com/gorilla/mux" - "golang.org/x/sys/unix" - - "go.awhk.org/fwdsms/pkg/twilio" -) - -var cfgFilename = flag.String("c", "/etc/fwdsms.yaml", "configuration file") - -func main() { - flag.Parse() - log.SetFlags(0) - fd, err := os.Open(*cfgFilename) - if err != nil { - log.Fatalf("Could not open the configuration file: %v.", err) - } - cfg, err := loadConfig(fd) - if err != nil { - log.Fatalf("Could not load the configuration: %v.", err) - } - - done := make(chan os.Signal, 1) - signal.Notify(done, os.Interrupt, unix.SIGTERM) - - sms := make(chan twilio.SMS) - - r := mux.NewRouter() - r.Path(cfg.Twilio.Endpoint). - Methods(http.MethodPost). - Handler(handlers.ProxyHeaders(&twilio.Filter{ - AuthToken: []byte(cfg.Twilio.AuthToken), - Handler: &twilio.SMSTee{ - Chan: sms, - Handler: twilio.EmptyResponseHandler, - }, - })) - srv := http.Server{Handler: r} - go func() { - var ( - l net.Listener - err error - ) - if cfg.Twilio.Address != "" && cfg.Twilio.Address[0] == '/' { - if l, err = net.Listen("unix", cfg.Twilio.Address); err != nil { - log.Fatalf("Could not set up UNIX listener: %v.", err) - } - if err = os.Chmod(cfg.Twilio.Address, 0666); err != nil { - log.Fatalf("Could not set up permissions on UNIX socket: %v.", err) - } - } else { - if cfg.Twilio.Address == "" { - cfg.Twilio.Address = ":8080" - } - if l, err = net.Listen("tcp", cfg.Twilio.Address); err != nil { - log.Fatalf("Could not set up TCP listener: %v.", err) - } - } - if err = srv.Serve(l); err != nil && err != http.ErrServerClosed { - log.Fatalf("Failed to serve HTTP: %v.", err) - } - }() - - mailer := newMailer(cfg, sms) - ctx, cancel := context.WithCancel(context.Background()) - go mailer.start(ctx) - - <-done - cancel() - ctx, cancel = context.WithTimeout(context.Background(), time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil && err != http.ErrServerClosed { - log.Fatalf("Failed to properly shut down the HTTP server: %v.", err) - } -} diff --git a/config.go b/config.go new file mode 100644 index 0000000..64a4a51 --- /dev/null +++ b/config.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +// SPDX-License-Identifier: ISC + +package main + +import ( + "io" + + "gopkg.in/yaml.v3" +) + +type Config struct { + Message Message `yaml:"message"` + SMTP SMTP `yaml:"smtp"` + Twilio Twilio `yaml:"twilio"` +} + +type Message struct { + From string `yaml:"from"` + To string `yaml:"to"` + Subject string `yaml:"subject"` + Template string `yaml:"template"` +} + +type SMTP struct { + Address string `yaml:"hostname"` + Username string `yaml:"username"` + Password string `yaml:"password"` +} + +type Twilio struct { + Address string `yaml:"address"` + AuthToken string `yaml:"authToken"` + Endpoint string `yaml:"endpoint"` +} + +func loadConfig(r io.Reader) (*Config, error) { + dec := yaml.NewDecoder(r) + cfg := &Config{} + if err := dec.Decode(cfg); err != nil { + return nil, err + } + return cfg, nil +} diff --git a/config_example.yaml b/config_example.yaml new file mode 100644 index 0000000..8da39e3 --- /dev/null +++ b/config_example.yaml @@ -0,0 +1,20 @@ +message: + from: foo@example.com + to: bar@example.com + subject: New SMS From {{.From}} For {{.To}} + template: | + From: {{.From}} + To: {{.To}} + Date: {{.DateReceived.UTC}} + + {{.Message}} + +smtp: + hostname: example.com:465 + username: bar + password: some password + +twilio: + address: /run/fwdsms/socket + authToken: some token + endpoint: / diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..a303719 --- /dev/null +++ b/config_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +const cfg string = ` +message: + from: foo@example.com + to: bar@example.com + subject: New SMS From {{.From}} For {{.To}} + template: | + From: {{.From}} + To: {{.To}} + Date: {{.DateReceived.UTC}} + + {{.Message}} + +smtp: + hostname: example.com:465 + username: bar + password: some password + +twilio: + address: /run/fwdsms/socket + authToken: some token + endpoint: / +` + +func TestConfig(t *testing.T) { + _, err := loadConfig(strings.NewReader(cfg)) + assert.NoError(t, err) +} diff --git a/configs/example.yaml b/configs/example.yaml deleted file mode 100644 index 8da39e3..0000000 --- a/configs/example.yaml +++ /dev/null @@ -1,20 +0,0 @@ -message: - from: foo@example.com - to: bar@example.com - subject: New SMS From {{.From}} For {{.To}} - template: | - From: {{.From}} - To: {{.To}} - Date: {{.DateReceived.UTC}} - - {{.Message}} - -smtp: - hostname: example.com:465 - username: bar - password: some password - -twilio: - address: /run/fwdsms/socket - authToken: some token - endpoint: / diff --git a/deployments/archlinux/PKGBUILD b/deployments/archlinux/PKGBUILD deleted file mode 100644 index 3dd3279..0000000 --- a/deployments/archlinux/PKGBUILD +++ /dev/null @@ -1,28 +0,0 @@ -# SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -# SPDX-License-Identifier: ISC - -# Maintainer: Grégoire Duchêne - -pkgname=fwdsms -pkgver=0.2.1 -pkgrel=1 -arch=(x86_64) -url=https://github.com/gduchene/fwdsms -license=(custom:ISC) -backup=(etc/${pkgname}.yaml) -makedepends=(go) -source=(git://github.com/gduchene/fwdsms.git#tag=v${pkgver}) -sha256sums=(SKIP) - -build() { - cd ${pkgname} - go build ./cmd/${pkgname} -} - -package() { - cd ${pkgname} - install -Dm755 ${pkgname} ${pkgdir}/usr/bin/${pkgname} - install -Dm644 configs/example.yaml ${pkgdir}/etc/${pkgname}.yaml - install -Dm644 deployments/systemd/${pkgname}.service ${pkgdir}/usr/lib/systemd/system/${pkgname}.service - install -Dm644 LICENSE ${pkgdir}/usr/share/licenses/${pkgname}/LICENSE -} diff --git a/deployments/docker/Dockerfile b/deployments/docker/Dockerfile deleted file mode 100644 index 6a04a6c..0000000 --- a/deployments/docker/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -# SPDX-License-Identifier: ISC - -FROM golang:1.16 -WORKDIR /root -COPY . ./ -RUN CGO_ENABLED=0 go build ./cmd/fwdsms - -FROM scratch -COPY --from=0 /root/fwdsms /usr/bin/fwdsms - -EXPOSE 8080 -ENTRYPOINT ["/usr/bin/fwdsms"] diff --git a/deployments/systemd/fwdsms.service b/deployments/systemd/fwdsms.service deleted file mode 100644 index a095805..0000000 --- a/deployments/systemd/fwdsms.service +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: © 2020 Grégoire Duchêne -# SPDX-License-Identifier: ISC - -[Unit] -Description=SMS-to-email Forwarder - -[Service] -ExecStart=fwdsms -DynamicUser=true -RuntimeDirectory=fwdsms - -[Install] -WantedBy=multi-user.target diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..fb5062d --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +# SPDX-License-Identifier: ISC + +FROM golang:1.16 +WORKDIR /root +COPY . ./ +ENV CGO_ENABLED=0 +RUN go build + +FROM scratch +COPY --from=0 /root/fwdsms /usr/bin/fwdsms + +EXPOSE 8080 +ENTRYPOINT ["/usr/bin/fwdsms"] diff --git a/mailer.go b/mailer.go new file mode 100644 index 0000000..cb687c9 --- /dev/null +++ b/mailer.go @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +// SPDX-License-Identifier: ISC + +package main + +import ( + "bytes" + "context" + "crypto/tls" + "fmt" + "log" + "net" + "net/smtp" + "text/template" + "time" + + "go.awhk.org/fwdsms/pkg/twilio" +) + +type email struct { + from, to string + body []byte +} + +type mailer struct { + auth smtp.Auth + hostname string + sms <-chan twilio.SMS + tmplFrom, tmplTo, tmplMsg *template.Template +} + +func (m *mailer) sendEmail(e email) error { + dialer := &net.Dialer{Timeout: time.Second} + conn, err := tls.DialWithDialer(dialer, "tcp", m.hostname, nil) + if err != nil { + return err + } + if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil { + log.Printf("Failed to set the SMTP connection deadline: %s.", err) + } + h, _, _ := net.SplitHostPort(m.hostname) + c, err := smtp.NewClient(conn, h) + if err != nil { + conn.Close() + return err + } + defer c.Close() + if err := c.Auth(m.auth); err != nil { + return err + } + + if err := c.Mail(e.from); err != nil { + return err + } + if err := c.Rcpt(e.to); err != nil { + return err + } + w, err := c.Data() + if err != nil { + return err + } + if _, err := w.Write(e.body); err != nil { + return err + } + if err = w.Close(); err != nil { + return nil + } + return c.Quit() +} + +func (m *mailer) newEmail(sms twilio.SMS) email { + var from, to, msg bytes.Buffer + if err := m.tmplFrom.Execute(&from, sms); err != nil { + log.Printf("Failed to apply a template: %v.", err) + } + if err := m.tmplTo.Execute(&to, sms); err != nil { + log.Printf("Failed to apply a template: %v.", err) + } + if err := m.tmplMsg.Execute(&msg, sms); err != nil { + log.Printf("Failed to apply a template: %v.", err) + } + return email{from.String(), to.String(), msg.Bytes()} +} + +func (m *mailer) start(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case sms := <-m.sms: + if err := m.sendEmail(m.newEmail(sms)); err != nil { + log.Printf("Failed to send email: %v.", err) + } + } + } +} + +func newMailer(cfg *Config, sms <-chan twilio.SMS) *mailer { + if cfg.Message.From == "" { + log.Fatal("Missing From field.") + } + if cfg.Message.To == "" { + log.Fatal("Missing To field.") + } + if cfg.Message.Subject == "" { + log.Fatal("Missing Subject field.") + } + if cfg.Message.Template == "" { + log.Fatal("Missing Template field.") + } + tmplMsg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s\r\n", + cfg.Message.From, cfg.Message.To, cfg.Message.Subject, cfg.Message.Template) + host, _, _ := net.SplitHostPort(cfg.SMTP.Address) + return &mailer{ + auth: smtp.PlainAuth("", cfg.SMTP.Username, cfg.SMTP.Password, host), + hostname: cfg.SMTP.Address, + sms: sms, + tmplFrom: template.Must(template.New("from").Parse(cfg.Message.From)), + tmplTo: template.Must(template.New("to").Parse(cfg.Message.To)), + tmplMsg: template.Must(template.New("message").Parse(tmplMsg)), + } +} diff --git a/mailer_test.go b/mailer_test.go new file mode 100644 index 0000000..e1376c3 --- /dev/null +++ b/mailer_test.go @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +// SPDX-License-Identifier: ISC + +package main + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "go.awhk.org/fwdsms/pkg/twilio" +) + +func TestMailer_newEmail(t *testing.T) { + m := newMailer(&Config{ + Message: Message{ + From: "fwdsms@example.com", + To: "sms{{.To}}@example.com", + Subject: "New SMS From {{.From}}", + Template: `From: {{.From}} + To: {{.To}} +Date: {{.DateReceived.UTC}} + +{{.Body}}`, + }}, nil) + // Reserved phone numbers, see Ofcom's website. + sms := twilio.SMS{ + DateReceived: time.Unix(0, 0), + From: "+442079460123", + To: "+447700900123", + Body: "Hello World!", + } + wants := email{ + from: "fwdsms@example.com", + to: "sms+447700900123@example.com", + body: []byte(strings.Join([]string{ + "From: fwdsms@example.com", + "To: sms+447700900123@example.com", + "Subject: New SMS From +442079460123", + "", + `From: +442079460123 + To: +447700900123 +Date: 1970-01-01 00:00:00 +0000 UTC + +Hello World!`, + "", + }, "\r\n")), + } + assert.Equal(t, wants, m.newEmail(sms)) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..090331f --- /dev/null +++ b/main.go @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +// SPDX-License-Identifier: ISC + +package main + +import ( + "context" + "flag" + "log" + "net" + "net/http" + "os" + "os/signal" + "time" + + "github.com/gorilla/handlers" + "github.com/gorilla/mux" + "golang.org/x/sys/unix" + + "go.awhk.org/fwdsms/pkg/twilio" +) + +var cfgFilename = flag.String("c", "/etc/fwdsms.yaml", "configuration file") + +func main() { + flag.Parse() + log.SetFlags(0) + fd, err := os.Open(*cfgFilename) + if err != nil { + log.Fatalf("Could not open the configuration file: %v.", err) + } + cfg, err := loadConfig(fd) + if err != nil { + log.Fatalf("Could not load the configuration: %v.", err) + } + + done := make(chan os.Signal, 1) + signal.Notify(done, os.Interrupt, unix.SIGTERM) + + sms := make(chan twilio.SMS) + + r := mux.NewRouter() + r.Path(cfg.Twilio.Endpoint). + Methods(http.MethodPost). + Handler(handlers.ProxyHeaders(&twilio.Filter{ + AuthToken: []byte(cfg.Twilio.AuthToken), + Handler: &twilio.SMSTee{ + Chan: sms, + Handler: twilio.EmptyResponseHandler, + }, + })) + srv := http.Server{Handler: r} + go func() { + var ( + l net.Listener + err error + ) + if cfg.Twilio.Address != "" && cfg.Twilio.Address[0] == '/' { + if l, err = net.Listen("unix", cfg.Twilio.Address); err != nil { + log.Fatalf("Could not set up UNIX listener: %v.", err) + } + if err = os.Chmod(cfg.Twilio.Address, 0666); err != nil { + log.Fatalf("Could not set up permissions on UNIX socket: %v.", err) + } + } else { + if cfg.Twilio.Address == "" { + cfg.Twilio.Address = ":8080" + } + if l, err = net.Listen("tcp", cfg.Twilio.Address); err != nil { + log.Fatalf("Could not set up TCP listener: %v.", err) + } + } + if err = srv.Serve(l); err != nil && err != http.ErrServerClosed { + log.Fatalf("Failed to serve HTTP: %v.", err) + } + }() + + mailer := newMailer(cfg, sms) + ctx, cancel := context.WithCancel(context.Background()) + go mailer.start(ctx) + + <-done + cancel() + ctx, cancel = context.WithTimeout(context.Background(), time.Second) + defer cancel() + if err := srv.Shutdown(ctx); err != nil && err != http.ErrServerClosed { + log.Fatalf("Failed to properly shut down the HTTP server: %v.", err) + } +} diff --git a/systemd/fwdsms.service b/systemd/fwdsms.service new file mode 100644 index 0000000..a095805 --- /dev/null +++ b/systemd/fwdsms.service @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: © 2020 Grégoire Duchêne +# SPDX-License-Identifier: ISC + +[Unit] +Description=SMS-to-email Forwarder + +[Service] +ExecStart=fwdsms +DynamicUser=true +RuntimeDirectory=fwdsms + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3-70-g09d2