Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
L11R committed Nov 29, 2018
0 parents commit e4fb656
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# jetbrains goland
.idea

# go modules
go.sum
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# go-autoconfig
IMAP/SMTP autodiscover feature for Thunderbird, Apple Mail and Microsoft Outlook

You need DNS SRV-record to get work Outlook and Thunderbird:
```
_autodiscover._tcp IN SRV 0 0 443 autoconfig.example.com.
```
Of course `autoconfig.example.com` domain should point to your server with this service.

### Thunderbird
`GET https://autoconfig.example.com/mail/config-v1.1.xml`

### Apple Mail
`GET https://autoconfig.example.com/email.mobileconfig?email=your@email.com`

### Outlook
`POST https://autoconfig.example.com/autodiscover/autodiscover.xml`
11 changes: 11 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
service_addr: ":1323"

domain: lord.sh

imap:
server: imap.mailbox.org
port: 993

smtp:
server: smtp.mailbox.org
port: 465
41 changes: 41 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package config

import (
"gopkg.in/go-playground/validator.v9"
"gopkg.in/yaml.v2"
"io/ioutil"
)

type Config struct {
ServiceAddr string `yaml:"service_addr"`

Domain string `yaml:"domain" validate:"required"`

IMAP *Server `yaml:"imap"`
SMTP *Server `yaml:"smtp"`
}

type Server struct {
Host string `yaml:"server" validate:"required"`
Port int `yaml:"port" validate:"required"`
STARTTLS bool `yaml:"starttls"`
}

func NewConfig(p string) (*Config, error) {
b, err := ioutil.ReadFile(p)
if err != nil {
return nil, err
}

var c Config
if err := yaml.Unmarshal(b, &c); err != nil {
return nil, err
}

validate := validator.New()
if err := validate.Struct(c); err != nil {
return nil, err
}

return &c, nil
}
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module go-autoconfig

require (
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/labstack/echo v3.3.8+incompatible
github.com/labstack/gommon v0.2.8 // indirect
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 // indirect
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.23.0
gopkg.in/yaml.v2 v2.2.2
)
119 changes: 119 additions & 0 deletions handlers/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package handlers

import (
"encoding/xml"
"github.com/labstack/echo"
"go-autoconfig/config"
"io/ioutil"
"net/http"
)

type Handler struct {
Config *config.Config
}

type server struct {
Host string
Port int
STARTTLS bool
}

func (h *Handler) Outlook(ctx echo.Context) error {
var req struct {
XMLName xml.Name `xml:"Autodiscover"`
Text string `xml:",chardata"`
Xmlns string `xml:"xmlns,attr"`
Request struct {
Text string `xml:",chardata"`
EMailAddress string `xml:"EMailAddress"`
AcceptableResponseSchema string `xml:"AcceptableResponseSchema"`
} `xml:"Request"`
}

b, err := ioutil.ReadAll(ctx.Request().Body)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}

if err := xml.Unmarshal(b, &req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}

data := struct {
Schema string
Email string
Domain string
IMAP *server
SMTP *server
}{
Schema: req.Request.AcceptableResponseSchema,
Email: req.Request.EMailAddress,
Domain: h.Config.Domain,
IMAP: &server{
Host: h.Config.IMAP.Host,
Port: h.Config.IMAP.Port,
STARTTLS: h.Config.IMAP.STARTTLS,
},
SMTP: &server{
Host: h.Config.SMTP.Host,
Port: h.Config.SMTP.Port,
STARTTLS: h.Config.SMTP.STARTTLS,
},
}

ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8)
return ctx.Render(http.StatusOK, "outlook", data)
}

func (h *Handler) Thunderbird(ctx echo.Context) error {
data := struct {
Domain string
IMAP *server
SMTP *server
}{
Domain: h.Config.Domain,
IMAP: &server{
Host: h.Config.IMAP.Host,
Port: h.Config.IMAP.Port,
STARTTLS: h.Config.IMAP.STARTTLS,
},
SMTP: &server{
Host: h.Config.SMTP.Host,
Port: h.Config.SMTP.Port,
STARTTLS: h.Config.SMTP.STARTTLS,
},
}

ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8)
return ctx.Render(http.StatusOK, "thunderbird", data)
}

func (h *Handler) AppleMail(ctx echo.Context) error {
var req struct {
Email string `query:"email"`
}
if err := ctx.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}

data := struct {
Email string
Domain string
IMAP *server
SMTP *server
}{
Email: req.Email,
Domain: h.Config.Domain,
IMAP: &server{
Host: h.Config.IMAP.Host,
Port: h.Config.IMAP.Port,
},
SMTP: &server{
Host: h.Config.SMTP.Host,
Port: h.Config.SMTP.Port,
},
}

ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationXMLCharsetUTF8)
return ctx.Render(http.StatusOK, "applemail", data)
}
61 changes: 61 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main // import "go-autoconfig"

import (
"flag"
"fmt"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"go-autoconfig/config"
"go-autoconfig/handlers"
"io"
"os"
"path/filepath"
"text/template"
)

var path = flag.String(
"config",
"",
"enter path to config file",
)

func main() {
// Parse at first startup
flag.Parse()

// Read config
conf, err := config.NewConfig(*path)
if err != nil {
fmt.Printf("Incorrect path or config itself! See help.\n%s\n", err.Error())
os.Exit(2)
}

tmpl := &Template{
templates: template.Must(template.ParseGlob(filepath.Join("templates", "*.tmpl"))),
}

// Init Echo
e := echo.New()
e.Renderer = tmpl

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Routes
h := handlers.Handler{conf}
e.POST("/autodiscover/autodiscover.xml", h.Outlook)
e.GET("/mail/config-v1.1.xml", h.Thunderbird)
e.GET("/email.mobileconfig", h.AppleMail)

// Start server
e.Logger.Fatal(e.Start(conf.ServiceAddr))
}

type Template struct {
templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
25 changes: 25 additions & 0 deletions templates/autoconfig.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{{define "thunderbird"}}<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
<emailProvider id="{{ .Domain }}">
<domain>{{ .Domain }}</domain>

<displayName>%EMAILADDRESS%</displayName>
<displayShortName>%EMAILLOCALPART%</displayShortName>

<incomingServer type="imap">
<hostname>{{ .IMAP.Host }}</hostname>
<port>{{ .IMAP.Port }}</port>
<socketType>{{ if .IMAP.STARTTLS }}STARTTLS{{ else }}SSL{{ end }}</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>

<outgoingServer type="smtp">
<hostname>{{ .SMTP.Host }}</hostname>
<port>{{ .SMTP.Port }}</port>
<socketType>{{ if .SMTP.STARTTLS }}STARTTLS{{ else }}SSL{{ end }}</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
</emailProvider>
</clientConfig>{{end}}
52 changes: 52 additions & 0 deletions templates/autodiscover.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{{ define "outlook" }}<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
<Response xmlns="{{ .Schema }}">
<User>
<DisplayName>{{ .Email }}</DisplayName>
</User>

<Account>
<AccountType>email</AccountType>
<Action>settings</Action>

<Protocol>
<Type>IMAP</Type>
<TTL>1</TTL>

<Server>{{ .IMAP.Host }}</Server>
<Port>{{ .IMAP.Port }}</Port>

<LoginName>{{ .Email }}</LoginName>

<DomainRequired>on</DomainRequired>
<DomainName>{{ .Domain }}</DomainName>

<SPA>off</SPA>
{{ if .IMAP.STARTTLS }}<TLS>on</TLS>{{ else }}<SSL>on</SSL>{{ end }}
<AuthRequired>on</AuthRequired>
</Protocol>
</Account>

<Account>
<AccountType>email</AccountType>
<Action>settings</Action>

<Protocol>
<Type>SMTP</Type>
<TTL>1</TTL>

<Server>{{ .SMTP.Host }}</Server>
<Port>{{ .SMTP.Port }}</Port>

<LoginName>{{ .Email }}</LoginName>

<DomainRequired>on</DomainRequired>
<DomainName>{{ .Domain }}</DomainName>

<SPA>off</SPA>
{{ if .SMTP.STARTTLS }}<TLS>on</TLS>{{ else }}<SSL>on</SSL>{{ end }}
<AuthRequired>on</AuthRequired>
</Protocol>
</Account>
</Response>
</Autodiscover>{{ end }}
Loading

0 comments on commit e4fb656

Please sign in to comment.