little update.

This commit is contained in:
saces 2026-01-31 08:13:53 +01:00
parent 4d60e5918d
commit 260386bcac
28 changed files with 1070 additions and 50 deletions

View file

@ -0,0 +1,125 @@
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
}

View file

@ -0,0 +1,59 @@
package mxpassfile
import (
"bytes"
"strings"
"testing"
)
func tokenComp(t *testing.T, expected string, value string) {
if value != expected {
t.Fatalf(`token was "%s", expected "%s"`, value, expected)
}
}
func unescape(s string) string {
s = strings.Replace(s, `\:`, `:`, -1)
s = strings.Replace(s, `\\`, `\`, -1)
return s
}
var passfile = [][]string{
{"test1:5432", "larrydb", "larry", "whatstheidea"},
{"test1:5432", "moedb", "moe", "imbecile"},
{"test1:5432", "curlydb", "curly", "nyuknyuknyuk"},
{"test2:5432", "*", "shemp", "heymoe"},
{"test2:5432", "*", "*", `test\\ing\|er`},
{"localhost", "*", "*", "sesam"},
{"test3", "", "", "swordfish"}, // user will be filled later
}
func TestParsePassFile(t *testing.T) {
buf := bytes.NewBufferString(`# A comment
test1:5432|larrydb|larry|whatstheidea
test1:5432|moedb|moe|imbecile
test1:5432|curlydb|curly|nyuknyuknyuk
test2:5432|*|shemp|heymoe
test2:5432|*|*|test\\ing\|er
localhost|*|*|sesam
`)
passfile, err := ParsePassfile(buf)
if err != nil {
t.Fatalf(`ParsePassfile returned error: "%v"`, err)
}
if len(passfile.Entries) != 6 {
t.Fatalf(`passfile.Entries is "%d", expected 6`, len(passfile.Entries))
}
tokenComp(t, "whatstheidea", passfile.FindPassword("test1:5432", "larrydb", "larry"))
tokenComp(t, "imbecile", passfile.FindPassword("test1:5432", "moedb", "moe"))
tokenComp(t, `test\ing|er`, passfile.FindPassword("test2:5432", "something", "else"))
tokenComp(t, "sesam", passfile.FindPassword("localhost", "foo", "bare"))
tokenComp(t, "", passfile.FindPassword("wrong:5432", "larrydb", "larry"))
tokenComp(t, "", passfile.FindPassword("test1:wrong", "larrydb", "larry"))
tokenComp(t, "", passfile.FindPassword("test1:5432", "wrong", "larry"))
tokenComp(t, "", passfile.FindPassword("test1:5432", "larrydb", "wrong"))
}

View file

@ -0,0 +1,9 @@
//go:build windows
// +build windows
package mxpassfile
// ReadPassfile reads the file at path and parses it into a Passfile.
func ReadPassfile(path string) (*Passfile, error) {
return readPassfile(path)
}

View file

@ -0,0 +1,22 @@
//go:build !windows
// +build !windows
package mxpassfile
import (
"errors"
"os"
)
// ReadPassfile reads the file at path and parses it into a Passfile.
func ReadPassfile(path string) (*Passfile, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return nil, err
}
permissions := fileInfo.Mode().Perm()
if permissions != 0o600 {
return nil, errors.New("To wide permissions, ignore file")
}
return readPassfile(path)
}