Compare commits

3 Commits

Author SHA1 Message Date
peio
6845060d00 fix: postfix socketmap protocol
All checks were successful
release / build (amd64, linux) (push) Successful in 29s
2026-01-19 17:18:37 +00:00
peio
762cb11389 rename aliasctl script and add it to release 2026-01-19 14:22:39 +00:00
peio
a7e6b5420e fix release workflow 2026-01-19 13:20:40 +00:00
3 changed files with 75 additions and 15 deletions

View File

@@ -25,7 +25,7 @@ jobs:
go-version: "1.22.x" go-version: "1.22.x"
cache: true cache: true
- name: Build (linux/musl) - name: Build
env: env:
GOOS: ${{ matrix.goos }} GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.arch }} GOARCH: ${{ matrix.arch }}
@@ -40,14 +40,17 @@ jobs:
-ldflags="-s -w -X main.version=${VERSION}" \ -ldflags="-s -w -X main.version=${VERSION}" \
-o "dist/${BIN}" ./cmd/kc-policy -o "dist/${BIN}" ./cmd/kc-policy
tar -C dist -czf "dist/${BIN}.tar.gz" "${BIN}" tar -C dist -czf "dist/${BIN}.tar.gz" "${BIN}"
cp aliasctl dist/
sha256sum "dist/${BIN}.tar.gz" >> dist/checksums.txt sha256sum "dist/${BIN}.tar.gz" >> dist/checksums.txt
sha256sum "dist/aliasctl" >> dist/checksums.txt
- name: Upload assets to Gitea release - name: Upload assets to Gitea release
uses: https://gitea.com/actions/gitea-release-action@v1 uses: https://gitea.com/actions/gitea-release-action@v1
with: with:
files: | files: |
dist/*.tar.gz dist/*.tar.gz
dist/*.sha256 dist/aliasctl
dist/checksums.txt
generate_release_notes: true generate_release_notes: true
env: env:
GITEA_TOKEN: ${{ github.token }} GITEA_TOKEN: ${{ github.token }}

View File

@@ -4,8 +4,10 @@ import (
"bufio" "bufio"
"context" "context"
"fmt" "fmt"
"io"
"net" "net"
"os" "os"
"strconv"
"strings" "strings"
) )
@@ -37,49 +39,104 @@ func RunSocketmap(ctx context.Context, cfg *Config, db *AliasDB) error {
} }
} }
// Socketmap protocol: "mapname key\n" -> "OK value\n" or "NOTFOUND\n" or "TEMP\n" // Postfix socketmap framing: "<len>:<payload>,"
func handleSocketmapConn(conn net.Conn, cfg *Config, db *AliasDB) { func handleSocketmapConn(conn net.Conn, cfg *Config, db *AliasDB) {
defer conn.Close() defer conn.Close()
r := bufio.NewReader(conn) r := bufio.NewReader(conn)
for { for {
line, err := r.ReadString('\n') payload, err := readSocketmapFrame(r)
if err != nil { if err != nil {
// normal close
return return
} }
line = strings.TrimSpace(line)
if line == "" { payload = strings.TrimSpace(payload)
if payload == "" {
_ = writeSocketmapFrame(conn, "NOTFOUND")
continue continue
} }
parts := strings.SplitN(line, " ", 2)
parts := strings.SplitN(payload, " ", 2)
if len(parts) != 2 { if len(parts) != 2 {
fmt.Fprint(conn, "TEMP\n") _ = writeSocketmapFrame(conn, "TEMP")
continue continue
} }
mapName := parts[0] mapName := parts[0]
key := strings.ToLower(strings.TrimSpace(parts[1])) key := strings.ToLower(strings.TrimSpace(parts[1]))
if mapName != "alias" { if mapName != "alias" {
fmt.Fprint(conn, "NOTFOUND\n") _ = writeSocketmapFrame(conn, "NOTFOUND")
continue continue
} }
// Only handle our domain // Only handle our domain
if !strings.HasSuffix(key, "@"+strings.ToLower(cfg.Policy.Domain)) { domain := strings.ToLower(cfg.Policy.Domain)
fmt.Fprint(conn, "NOTFOUND\n") if !strings.HasSuffix(key, "@"+domain) {
_ = writeSocketmapFrame(conn, "NOTFOUND")
continue continue
} }
username, ok, err := db.AliasOwner(key) username, ok, err := db.AliasOwner(key)
if err != nil { if err != nil {
fmt.Fprint(conn, "TEMP\n") _ = writeSocketmapFrame(conn, "TEMP")
continue continue
} }
if !ok { if !ok {
fmt.Fprint(conn, "NOTFOUND\n") _ = writeSocketmapFrame(conn, "NOTFOUND")
continue continue
} }
// rewrite alias -> primary rcpt (username@domain)
fmt.Fprintf(conn, "OK %s@%s\n", username, strings.ToLower(cfg.Policy.Domain)) // rewrite alias -> username@domain
_ = writeSocketmapFrame(conn, fmt.Sprintf("OK %s@%s", username, domain))
} }
} }
func readSocketmapFrame(r *bufio.Reader) (string, error) {
// read decimal length until ':'
var lenBuf strings.Builder
for {
b, err := r.ReadByte()
if err != nil {
return "", err
}
if b == ':' {
break
}
if b < '0' || b > '9' {
return "", io.ErrUnexpectedEOF
}
lenBuf.WriteByte(b)
if lenBuf.Len() > 10 {
return "", io.ErrUnexpectedEOF
}
}
n, err := strconv.Atoi(lenBuf.String())
if err != nil || n < 0 || n > 1024*1024 {
return "", io.ErrUnexpectedEOF
}
buf := make([]byte, n)
if _, err := io.ReadFull(r, buf); err != nil {
return "", err
}
// expect trailing comma
b, err := r.ReadByte()
if err != nil {
return "", err
}
if b != ',' {
return "", io.ErrUnexpectedEOF
}
return string(buf), nil
}
func writeSocketmapFrame(w io.Writer, payload string) error {
// "<len>:<payload>,"
_, err := fmt.Fprintf(w, "%d:%s,", len(payload), payload)
return err
}