pygomx/libmxclient/determinant/mxpassfile/mxpassfile.go
2026-01-31 15:02:30 +01:00

125 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
}