reworked templates so they're not read from disk each time

This commit is contained in:
Paul Wilde 2021-08-18 12:45:30 +01:00
parent 4fa86d82f0
commit b093b46cea
7 changed files with 156 additions and 61 deletions

View file

@ -1,5 +1,5 @@
--- ---
Version : "0.1.2" Version : "0.1.3"
# Sample config.yaml file. # Sample config.yaml file.
# Copy this file to "config/config.yaml" and adjust the # Copy this file to "config/config.yaml" and adjust the
# settings to your requirements # settings to your requirements

View file

@ -6,18 +6,35 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"encoding/json" "encoding/json"
"text/template"
"path"
"regexp"
"strings"
"time"
// "reflect"
) )
// Global variables // Global variables
var ThisSession Session var ThisSession Session
var MainConfig Config var MainConfig Config
// Template declaration
var templates []string = []string{"autodiscover.xml","autoconfig.xml"}
var Templates map[string]*template.Template = make(map[string]*template.Template)
const defaultConfigDir string = "default-config/" const defaultConfigDir string = "default-config/"
const configDir string = "config/" const configDir string = "config/"
func NewSessionID() string{
timecode := time.Now()
id := timecode.Format("20060102150405.000")
id = strings.Replace(id,".","",1)
return id
}
func NewConfig() Config { func NewConfig() Config {
MainConfig = loadConfig() MainConfig = loadConfig()
loadXMLTemplates()
return MainConfig return MainConfig
} }
func loadConfig() Config { func loadConfig() Config {
@ -37,7 +54,22 @@ func loadConfig() Config {
removeDisabledItems(&cfg) removeDisabledItems(&cfg)
return cfg return cfg
} }
func loadXMLTemplates(){
for _, tmpl := range templates {
tmpl := fmt.Sprintf("templates/%s",tmpl)
name := path.Base(tmpl)
var fmap = template.FuncMap{
"lower": strings.ToLower,
"parseUsername": parseUsername,
"onoff": chooseOnOff,
}
t, err := template.New(name).Funcs(fmap).ParseFiles(tmpl)
if err != nil {
panic (err)
}
Templates[name] = t
}
}
func unmarshalConfig(file string, cfg *Config) { func unmarshalConfig(file string, cfg *Config) {
if FileExists(file) { if FileExists(file) {
content, err := ioutil.ReadFile(file) content, err := ioutil.ReadFile(file)
@ -94,3 +126,40 @@ func JSONify(content interface{}) string {
} }
return string(data) return string(data)
} }
func parseUsername(svc Service, email string) string {
if email == "" {
return "not-provided"
}
if svc.UsernameIsFQDN && !svc.RequireLocalDomain{
return email
} else if svc.UsernameIsFQDN && svc.RequireLocalDomain {
re := regexp.MustCompile(`[^@(%40)]+$`)
domain := re.FindString(email)
localemail := strings.Replace(email, domain,
MainConfig.LocalDomain,1)
return localemail
} else {
re := regexp.MustCompile(`^[^@(%40)]+`)
username := re.FindString(email)
return username
}
}
func chooseOnOff(value bool) string {
if value {
return "on"
} else {
return "off"
}
}
// GetIP gets a requests IP address by reading off the forwarded-for
// header (for proxies) and falls back to use the remote address.
func GetSessionIP() string {
r := ThisSession.Request
ip := r.RemoteAddr
forwarded := r.Header.Get("X-FORWARDED-FOR")
if forwarded != "" {
ip = forwarded
}
fmt.Printf("Session %s Connect From : %s\r\f",ThisSession.ID, ip)
return ip
}

View file

@ -4,12 +4,15 @@ import "net/http"
type Session struct { type Session struct {
ID string
IP string
ResponseWriter http.ResponseWriter ResponseWriter http.ResponseWriter
Request *http.Request Request *http.Request
Path string Path string
WebContent string WebContent string
ContentType string ContentType string
} }
type Config struct { type Config struct {
Version string `yaml:"Version"` Version string `yaml:"Version"`
BaseURL string `yaml:"BaseURL"` BaseURL string `yaml:"BaseURL"`

View file

@ -9,7 +9,7 @@
<hostname>{{ .Server }}</hostname> <hostname>{{ .Server }}</hostname>
<port>{{ .Port }}</port> <port>{{ .Port }}</port>
<socketType>{{ .SocketType }}</socketType> <socketType>{{ .SocketType }}</socketType>
<username>{{ . | parseUsername }}</username> <username>{{ $.Email | parseUsername . }}</username>
<authentication>{{ .Authentication }}</authentication> <authentication>{{ .Authentication }}</authentication>
</incomingServer> </incomingServer>
{{ end }} {{ end }}
@ -20,7 +20,7 @@
<hostname>{{ .Server }}</hostname> <hostname>{{ .Server }}</hostname>
<port>{{ .Port }}></port> <port>{{ .Port }}></port>
<socketType>{{ .SocketType }}</socketType> <socketType>{{ .SocketType }}</socketType>
<username>{{ . | parseUsername }}</username> <username>{{ $.Email | parseUsername . }}</username>
<authentication>{{ .Authentication }}</authentication> <authentication>{{ .Authentication }}</authentication>
</outgoingServer> </outgoingServer>
{{ end }} {{ end }}
@ -28,7 +28,7 @@
{{ with .Config.AddressBook }} {{ with .Config.AddressBook }}
{{ if .Enabled }} {{ if .Enabled }}
<addressBook type="{{ .Type | lower }}"> <addressBook type="{{ .Type | lower }}">
<username>{{ . | parseUsername }}</username> <username>{{ $.Email | parseUsername . }}</username>
<authentication>{{ .Authentication }}</authentication> <authentication>{{ .Authentication }}</authentication>
<serverURL>{{ .Server }}</serverURL> <serverURL>{{ .Server }}</serverURL>
</addressBook> </addressBook>
@ -37,7 +37,7 @@
{{ with .Config.Calendar }} {{ with .Config.Calendar }}
{{ if .Enabled }} {{ if .Enabled }}
<calendar type="{{ .Type | lower }}"> <calendar type="{{ .Type | lower }}">
<username>{{ . | parseUsername }}</username> <username>{{ $.Email | parseUsername . }}</username>
<authentication>{{ .Authentication }}</authentication> <authentication>{{ .Authentication }}</authentication>
<serverURL>{{ .Server }}</serverURL> <serverURL>{{ .Server }}</serverURL>
</calendar> </calendar>
@ -48,7 +48,7 @@
<webMail> <webMail>
<loginPage url="{{ .Server }}" /> <loginPage url="{{ .Server }}" />
<loginPageInfo url="{{ .Server }}"> <loginPageInfo url="{{ .Server }}">
<username>{{ . | parseUsername }}</username> <username>{{ $.Email | parseUsername . }}</username>
<usernameField id="{{ .UsernameDivID }}" name="{{ .UsernameDivID }}" /> <usernameField id="{{ .UsernameDivID }}" name="{{ .UsernameDivID }}" />
<passwordField name="{{ .PasswordDivName }}" /> <passwordField name="{{ .PasswordDivName }}" />
<loginButton id="{{ .SubmitButtonID }}" name="{{ .SubmitButtonName }}"/> <loginButton id="{{ .SubmitButtonID }}" name="{{ .SubmitButtonName }}"/>

View file

@ -11,7 +11,7 @@
<Server>{{ .Server }}</Server> <Server>{{ .Server }}</Server>
<Port>{{ .Port }}</Port> <Port>{{ .Port }}</Port>
<DomainRequired>{{ .UsernameIsFQDN | onoff }}</DomainRequired> <DomainRequired>{{ .UsernameIsFQDN | onoff }}</DomainRequired>
<LoginName>{{ . | parseUsername }}</LoginName> <LoginName>{{ $.Email | parseUsername . }}</LoginName>
<SPA>{{ .SPA | onoff }}</SPA> <SPA>{{ .SPA | onoff }}</SPA>
<SSL>{{ if eq .SocketType "SSL" }}on{{ else }}off{{ end }}</SSL> <SSL>{{ if eq .SocketType "SSL" }}on{{ else }}off{{ end }}</SSL>
<AuthRequired>{{ not .NoAuthRequired | onoff }}</AuthRequired> <AuthRequired>{{ not .NoAuthRequired | onoff }}</AuthRequired>
@ -25,7 +25,7 @@
<Server>{{ .Server }}</Server> <Server>{{ .Server }}</Server>
<Port>{{ .Port }}</Port> <Port>{{ .Port }}</Port>
<DomainRequired>{{ .UsernameIsFQDN | onoff }}</DomainRequired> <DomainRequired>{{ .UsernameIsFQDN | onoff }}</DomainRequired>
<LoginName>{{ . | parseUsername }}</LoginName> <LoginName>{{ $.Email | parseUsername . }}</LoginName>
<SPA>{{ .SPA | onoff }}</SPA> <SPA>{{ .SPA | onoff }}</SPA>
<Encryption>{{ .SocketType }}</Encryption> <Encryption>{{ .SocketType }}</Encryption>
<AuthRequired>{{ not .NoAuthRequired | onoff }}</AuthRequired> <AuthRequired>{{ not .NoAuthRequired | onoff }}</AuthRequired>

View file

@ -8,10 +8,16 @@ import (
"fmt" "fmt"
) )
func WebHandler(w http.ResponseWriter, r *http.Request) { func WebHandler(w http.ResponseWriter, r *http.Request) {
fmt.Println("Request For :",r.URL)
ThisSession = Session{} ThisSession = Session{}
ThisSession.ResponseWriter = w ThisSession.ResponseWriter = w
ThisSession.Request = r ThisSession.Request = r
ThisSession.ID = NewSessionID()
fmt.Printf("Session %s Request For : %s\r\f",ThisSession.ID, r.URL)
ThisSession.IP = GetSessionIP()
ThisSession.Path = strings.ToLower(r.URL.Path[1:]) ThisSession.Path = strings.ToLower(r.URL.Path[1:])
if ThisSession.Path == "" { if ThisSession.Path == "" {
ThisSession.Path = "none" ThisSession.Path = "none"

View file

@ -2,42 +2,47 @@ package responses
import ( import (
"mailautoconf/global" "mailautoconf/global"
. "mailautoconf/structs" . "mailautoconf/structs"
"text/template" // "text/template"
"fmt" "fmt"
"path" // "path"
"strings" "strings"
"bytes" "bytes"
"regexp" "regexp"
) )
var email string var email string
var fmap = template.FuncMap{
"lower": strings.ToLower,
"parseUsername": parseUsername,
"onoff": chooseOnOff,
}
func MozAutoconfig() string { func MozAutoconfig() string {
// The below link has config-v1.1.xml information // The below link has config-v1.1.xml information
// https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat // https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat
tmpl := "templates/autoconfig.xml"
// parse the querystring
if err := global.ThisSession.Request.ParseForm(); err != nil {
fmt.Println(err)
}
// build the response
response := Response{} response := Response{}
response.Email = global.ThisSession.Request.FormValue("emailaddress") response.Email = global.ThisSession.Request.FormValue("emailaddress")
email = response.Email email = response.Email
response.Config = global.MainConfig response.Config = global.MainConfig
name := path.Base(tmpl) // set content type to XML
t, err1 := template.New(name).Funcs(fmap).ParseFiles(tmpl)
if err1 != nil {
panic (err1)
}
global.ThisSession.ContentType = "application/xml" global.ThisSession.ContentType = "application/xml"
// execute the template
var result bytes.Buffer var result bytes.Buffer
err := t.Execute(&result, response) template := global.Templates["autoconfig.xml"]
err := template.Execute(&result, response)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
// return our string of xml
return result.String() return result.String()
} }
func MsAutoDiscoverXML() string { func MsAutoDiscoverXML() string {
// MS Outlook Autodiscover.xml
//
// Example POST Request (sent from client) : // Example POST Request (sent from client) :
// <?xml version="1.0" \?\> // <?xml version="1.0" \?\>
// <Autodiscover xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"> // <Autodiscover xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
@ -46,27 +51,64 @@ func MsAutoDiscoverXML() string {
// <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema> // <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
// </Request> // </Request>
// </Autodiscover> // </Autodiscover>
tmpl := "templates/autodiscover.xml"
email = global.ThisSession.Request.FormValue("EMailAddress") // Parse the form to get the values
response := Response{} if err := global.ThisSession.Request.ParseForm(); err != nil {
response.Config = global.MainConfig fmt.Println(err)
name := path.Base(tmpl)
t, err1 := template.New(name).Funcs(fmap).ParseFiles(tmpl)
if err1 != nil {
panic (err1)
} }
// convert the input to a string so we can extract the email address
form := fmt.Sprintf("%s",global.ThisSession.Request.Form)
// fine the EMailAddress section
find := regexp.MustCompile(`\<EMailAddress\>(.*?)\<\/EMailAddress\>`)
email = find.FindString(form)
// replace the tags
replace := regexp.MustCompile(`\<[\/]?EMailAddress\>`)
email = replace.ReplaceAllString(email,``)
fmt.Printf("Session %s Request for email : %s\r\f",global.ThisSession.ID,email)
// build the reponse
response := Response{}
response.Email = email
response.Config = global.MainConfig
// execute the template
template := global.Templates["autodiscover.xml"]
global.ThisSession.ContentType = "application/xml" global.ThisSession.ContentType = "application/xml"
var result bytes.Buffer var result bytes.Buffer
err := t.Execute(&result, response) err := template.Execute(&result, response)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
// return our string of xml
return result.String() return result.String()
} }
func MsAutoDiscoverJSON() string { func MsAutoDiscoverJSON() string {
// MS Outlook Autodiscover.json - undocumented
//
// Example Request // Example Request
// /autodiscover/autodiscover.json?Email=you@your.domain&Protocol=Autodiscoverv1&RedirectCount=1 // /autodiscover/autodiscover.json?Email=you@your.domain&Protocol=Autodiscoverv1&RedirectCount=1
return "" email = global.ThisSession.Request.FormValue("Email")
protocol := global.ThisSession.Request.FormValue("Protocol")
fmt.Println(protocol)
global.ThisSession.ContentType = "application/json"
switch strings.ToLower(protocol) {
case "autodiscoverv1":
response := MSAutodiscoverJSONResponse{}
response.Protocol = "AutodiscoverV1"
response.Url = fmt.Sprintf("%s/Autodiscover/Autodiscover.xml", global.MainConfig.BaseURL)
return global.JSONify(response)
default:
response := MSAutodiscoverJSONError{}
response.ErrorCode = "InvalidProtocol";
response.ErrorMessage = fmt.Sprintf("The given protocol value '%s' is invalid. Supported values are 'AutodiscoverV1'", protocol)
return global.JSONify(response)
}
} }
func DefaultResponse() string { func DefaultResponse() string {
response := Response{} response := Response{}
@ -81,28 +123,3 @@ func OurConfig() string {
content := global.JSONify(global.MainConfig) content := global.JSONify(global.MainConfig)
return content return content
} }
func parseUsername(svc Service) string {
if email == "" {
return "not-provided"
}
if svc.UsernameIsFQDN && !svc.RequireLocalDomain{
return email
} else if svc.UsernameIsFQDN && svc.RequireLocalDomain {
re := regexp.MustCompile(`[^@(%40)]+$`)
domain := re.FindString(email)
localemail := strings.Replace(email, domain,
global.MainConfig.LocalDomain,1)
return localemail
} else {
re := regexp.MustCompile(`^[^@(%40)]+`)
username := re.FindString(email)
return username
}
}
func chooseOnOff(value bool) string {
if value {
return "on"
} else {
return "off"
}
}