initial POC

This commit is contained in:
2024-06-04 22:46:09 +02:00
commit d3a672694f
12 changed files with 781 additions and 0 deletions

62
internal/app/app.go Normal file
View File

@@ -0,0 +1,62 @@
package app
import (
"fmt"
"log"
"text/template"
"git.asperti.com/paspo/mail-autoconfig/internal/config"
"git.asperti.com/paspo/mail-autoconfig/internal/db"
"github.com/gin-gonic/gin"
)
var Domains map[string]Domain
var Router *gin.Engine
func init() {
var err error
Domains = make(map[string]Domain)
thunderbirdTemplate, err = template.New("thunderbird").Parse(thunderbirdTpl)
if err != nil {
log.Fatal(err) // TODO
}
outlookTemplate, err = template.New("outlook").Parse(outlookTpl)
if err != nil {
log.Fatal(err) // TODO
}
Router = gin.Default()
Router.GET("/.well-known/autoconfig/mail/config-v1.1.xml", RenderThunderbird)
Router.GET("/mail/config-v1.1.xml", RenderThunderbird)
Router.POST("/autodiscover/autodiscover.xml", RenderOutlook)
}
func FetchAllDomains() error {
var err error
rows, err := db.DB.Queryx("select * from domains")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var d Domain
err := rows.StructScan(&d)
if err != nil {
return err
}
Domains[d.Domain] = d
}
err = rows.Err()
if err != nil {
return err
}
return nil
}
func Start() {
address := fmt.Sprintf(":%d", config.AppConfig.HttpPort)
Router.Run(address)
}

26
internal/app/domain.go Normal file
View File

@@ -0,0 +1,26 @@
package app
type Domain struct {
Domain string `db:"domain"`
DisplayName string `db:"display_name"`
DisplayShortName string `db:"display_shortname"`
IMAPEnabled bool `db:"imap_enabled"`
IMAPServer string `db:"imap_server"`
IMAPPort int `db:"imap_port"`
IMAPSSL bool `db:"imap_ssl"`
IMAPSPA bool `db:"imap_spa"`
POP3Enabled bool `db:"pop3_enabled"`
POP3Server string `db:"pop3_server"`
POP3Port int `db:"pop3_port"`
POP3SSL bool `db:"pop3_ssl"`
POP3SPA bool `db:"pop3_spa"`
SMTPEnabled bool `db:"smtp_enabled"`
SMTPServer string `db:"smtp_server"`
SMTPPort int `db:"smtp_port"`
SMTPSSL bool `db:"smtp_ssl"`
SMTPTLS bool `db:"smtp_tls"`
SMTPSPA bool `db:"smtp_spa"`
POPBeforeSMTP bool `db:"pop_before_smtp"`
DomainRequired bool `db:"domain_required"`
Username string
}

135
internal/app/outlook.go Normal file
View File

@@ -0,0 +1,135 @@
package app
import (
"bytes"
"encoding/xml"
"io"
"log"
"net/http"
"strings"
"text/template"
"github.com/gin-gonic/gin"
)
const outlookTpl = `
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
<Account>
<AccountType>email</AccountType>
<Action>settings</Action>
{{- if .IMAPEnabled }}
<Protocol>
<Type>IMAP</Type>
<Server>{{ .IMAPServer }}</Server>
<Port>{{ .IMAPPort }}</Port>
<DomainRequired>on</DomainRequired>
<SPA>off</SPA>
<SSL>{{ if .IMAPSSL }}on{{ else }}off{{ end }}</SSL>
<AuthRequired>on</AuthRequired>
<LoginName>{{ .Username }}@{{ .Domain }}</LoginName>
</Protocol>
{{- end }}
{{- if .POP3Enabled }}
<Protocol>
<Type>POP3</Type>
<Server>mail.mdfmultimedia.com</Server>
<Port>995</Port>
<DomainRequired>on</DomainRequired>
<SPA>off</SPA>
<SSL>{{ if .POP3SSL }}on{{ else }}off{{ end }}</SSL>
<AuthRequired>on</AuthRequired>
<LoginName>{{ .Username }}@{{ .Domain }}</LoginName>
</Protocol>
{{- end }}
{{- if .SMTPEnabled }}
<Protocol>
<Type>SMTP</Type>
<Server>mail.mdfmultimedia.com</Server>
<Port>465</Port>
<DomainRequired>on</DomainRequired>
<SPA>off</SPA>
<SSL>{{ if .SMTPSSL }}on{{ else }}off{{ end }}</SSL>
<AuthRequired>on</AuthRequired>
<LoginName>{{ .Username }}@{{ .Domain }}</LoginName>
</Protocol>
{{- end }}
</Account>
</Response>
</Autodiscover>
`
var outlookTemplate *template.Template
func RenderOutlook(g *gin.Context) {
var err error
var body []byte
var domain Domain
var ok bool
var b bytes.Buffer
body, err = io.ReadAll(g.Request.Body)
if err != nil {
g.JSON(404, gin.H{"code": "WRONG_BODY", "message": "malformed request"})
return
}
var e struct {
Address string `xml:"Request>EMailAddress"`
}
err = xml.Unmarshal(body, &e)
if err != nil {
g.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
return
}
components := strings.Split(e.Address, "@")
username, reqDomain := components[0], components[1]
domain, ok = Domains[reqDomain]
if !ok {
g.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
return
}
domain.Username = username
err = outlookTemplate.Execute(&b, domain)
if err != nil {
log.Fatal(err)
}
g.Header("Content-Type", "application/xml; charset=utf-8")
g.String(http.StatusOK, b.String())
}
// curl -X POST -d @req.xml http://127.0.0.1:8888/autodiscover/autodiscover.xml
// curl --basic -X "POST" -u XXX@YYY -v https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml -H "Content-Type: text/xml" -d @a.txt
/*
<?xml version="1.0" encoding="utf-8"?>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/mobilesync/requestschema/2006">
<Request>
<EMailAddress>testuser@company.tld</EMailAddress>
<AcceptableResponseSchema>
http://schemas.microsoft.com/exchange/autodiscover/mobilesync/responseschema/2006
</AcceptableResponseSchema>
</Request>
</Autodiscover>
$ curl -d @request.xml -u testuser@company.tld -H "Content-Type: text/xml" -v https://autodiscover.company.tld/autodiscover/autodiscover.xml
*/
/*
1 - Lookup di un record A (o CNAME) per contoso.com che punta ad un web server che risponderà con un URL HTTPS https://contoso.com/Autodiscover/Autodiscover.xml.
2 - Lookup di un record A (o CNAME) per autodiscover.contoso.com che punta ad un web server che risponderà con un URL HTTPS https://autodiscover.contoso.com/Autodiscover/Autodiscover.xml
3 - Lookup di un record A (o CNAME) per contoso.com che punta ad un web server che risponderà con URL HTTP http://autodiscover.contoso.com/Autodiscover/Autodiscover.xml (se avete un reverse proxy o direttamente su Exchange, dovrete fare la configurazione necessaria per implementare lhttp to https redirect)
4 - Lookup di un record SRV per autodiscover._tcp.contoso.com (Questo record deve contenere la porta 443 e lhostname, ad esempio mail.contoso.com, permetendo cosi al client di fare una request in https allURL https://mail.contoso.com/Autodiscover/Autodiscover.xml)
*/

View File

@@ -0,0 +1,90 @@
package app
import (
"bytes"
"log"
"net/http"
"text/template"
"github.com/gin-gonic/gin"
)
const thunderbirdTpl = `
<clientConfig version="1.1">
<emailProvider id="{{ .Domain }}">
<domain>{{ .Domain }}</domain>
<displayName>{{ .DisplayName }}</displayName>
<displayShortName>{{ .DisplayShortName }}</displayShortName>
{{- if .IMAPEnabled }}
<incomingServer type="imap">
<hostname>{{ .IMAPServer }}</hostname>
<port>{{ .IMAPPort }}</port>
{{- if .IMAPSSL }}
<socketType>SSL</socketType>
{{- else }}
<socketType>plain</socketType>
{{- end }}
<authentication>password-encrypted</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
{{- end }}
{{- if .POP3Enabled }}
<incomingServer type="pop3">
<hostname>{{ .POP3Server }}</hostname>
<port>{{ .POP3Port }}</port>
{{- if .POP3SSL }}
<socketType>SSL</socketType>
{{- else }}
<socketType>plain</socketType>
{{- end }}
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
{{- end }}
{{- if .SMTPEnabled }}
<outgoingServer type="smtp">
<hostname>{{ .SMTPServer }}</hostname>
<port>{{ .SMTPPort }}</port>
{{- if .SMTPSSL }}
<socketType>SSL</socketType>
{{- else }}
{{if .SMTPTLS }}
<socketType>STARTTLS</socketType>
{{- else }}
<socketType>plain</socketType>
{{- end }}
{{- end }}
<authentication>password-encrypted</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
{{- end }}
</emailProvider>
</clientConfig>
`
var thunderbirdTemplate *template.Template
// https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
func RenderThunderbird(g *gin.Context) {
host := g.Request.Host
domain, ok := Domains[host]
if !ok {
g.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"})
return
}
var b bytes.Buffer
if err := thunderbirdTemplate.Execute(&b, domain); err != nil {
log.Fatal(err)
}
g.Header("Content-Type", "application/xml; charset=utf-8")
g.String(http.StatusOK, b.String())
}
// curl http://127.0.0.1:8888/.well-known/autoconfig/mail/config-v1.1.xml