126 lines
2.7 KiB
Go
126 lines
2.7 KiB
Go
|
|
package mxpassfile
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// inspired by https://github.com/jackc/pgpassfile
|
||
|
|
|
||
|
|
// Entry represents a line in a MX passfile.
|
||
|
|
type Entry struct {
|
||
|
|
Matrixhost string
|
||
|
|
Localpart string
|
||
|
|
Domain string
|
||
|
|
Token string
|
||
|
|
}
|
||
|
|
|
||
|
|
// Passfile is the in memory data structure representing a MX passfile.
|
||
|
|
type Passfile struct {
|
||
|
|
Entries []*Entry
|
||
|
|
}
|
||
|
|
|
||
|
|
// ReadPassfile reads the file at path and parses it into a Passfile.
|
||
|
|
func readPassfile(path string) (*Passfile, error) {
|
||
|
|
f, err := os.Open(path)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
|
||
|
|
return ParsePassfile(f)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ParsePassfile reads r and parses it into a Passfile.
|
||
|
|
func ParsePassfile(r io.Reader) (*Passfile, error) {
|
||
|
|
passfile := &Passfile{}
|
||
|
|
|
||
|
|
scanner := bufio.NewScanner(r)
|
||
|
|
for scanner.Scan() {
|
||
|
|
entry := parseLine(scanner.Text())
|
||
|
|
if entry != nil {
|
||
|
|
passfile.Entries = append(passfile.Entries, entry)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return passfile, scanner.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
// parseLine parses a line into an *Entry. It returns nil on comment lines or any other unparsable
|
||
|
|
// line.
|
||
|
|
func parseLine(line string) *Entry {
|
||
|
|
const (
|
||
|
|
tmpBackslash = "\r"
|
||
|
|
tmpPipe = "\n"
|
||
|
|
)
|
||
|
|
|
||
|
|
line = strings.TrimSpace(line)
|
||
|
|
|
||
|
|
if strings.HasPrefix(line, "#") {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
line = strings.ReplaceAll(line, `\\`, tmpBackslash)
|
||
|
|
line = strings.ReplaceAll(line, `\|`, tmpPipe)
|
||
|
|
|
||
|
|
parts := strings.Split(line, "|")
|
||
|
|
if len(parts) != 4 {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Unescape escaped colons and backslashes
|
||
|
|
for i := range parts {
|
||
|
|
parts[i] = strings.ReplaceAll(parts[i], tmpBackslash, `\`)
|
||
|
|
parts[i] = strings.ReplaceAll(parts[i], tmpPipe, `|`)
|
||
|
|
parts[i] = strings.TrimSpace(parts[i])
|
||
|
|
}
|
||
|
|
|
||
|
|
return &Entry{
|
||
|
|
Matrixhost: parts[0],
|
||
|
|
Localpart: parts[1],
|
||
|
|
Domain: parts[2],
|
||
|
|
Token: parts[3],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func isWild(s string) bool {
|
||
|
|
if s == "" || s == "*" {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
func superCMP(s1, s2 string) bool {
|
||
|
|
if isWild(s1) || isWild(s2) {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
//fmt.Printf("sCMP: '%s' '%s'\n", s1, s2)
|
||
|
|
return s1 == s2
|
||
|
|
}
|
||
|
|
|
||
|
|
// FindPassword finds the password for the provided synapsehost, localpart, and domain. An empty
|
||
|
|
// string will be returned if no match is found.
|
||
|
|
func (pf *Passfile) FindPassword(matrixhost, localpart, domain string) string {
|
||
|
|
for _, e := range pf.Entries {
|
||
|
|
if (e.Matrixhost == "*" || e.Matrixhost == matrixhost) &&
|
||
|
|
(e.Localpart == "*" || e.Localpart == localpart) &&
|
||
|
|
(e.Domain == "*" || e.Domain == domain) {
|
||
|
|
return e.Token
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
func (pf *Passfile) FindPasswordFill(matrixhost, localpart, domain string) *Entry {
|
||
|
|
for _, e := range pf.Entries {
|
||
|
|
if superCMP(e.Matrixhost, matrixhost) &&
|
||
|
|
superCMP(e.Localpart, localpart) &&
|
||
|
|
superCMP(e.Domain, domain) {
|
||
|
|
return e
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|