From 10587f425b3e0e75ee312a66e4bb04ec28f4b5af Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sat, 10 Sep 2022 17:13:20 +0000 Subject: [PATCH] auth: add PAM support Handy for small local installations. Disabled by default because it adds a dependency on the PAM library. --- README.md | 1 + auth/pam.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ auth/pam_stub.go | 11 +++++++ auth/url.go | 2 ++ go.mod | 5 ++- go.sum | 2 ++ 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 auth/pam.go create mode 100644 auth/pam_stub.go diff --git a/README.md b/README.md index 9481bf4..1b8c75b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ It currently provides: Authentication: * IMAP (working) +* PAM (working, enabled via the `pam` build tag) Storage: diff --git a/auth/pam.go b/auth/pam.go new file mode 100644 index 0000000..bd94e12 --- /dev/null +++ b/auth/pam.go @@ -0,0 +1,79 @@ +//go:build pam + +package auth + +import ( + "fmt" + "net/http" + + "github.com/msteinert/pam" + + "git.sr.ht/~sircmpwn/tokidoki/debug" +) + +type pamProvider struct{} + +func NewPAM() (AuthProvider, error) { + return pamProvider{}, nil +} + +func (pamProvider) Middleware() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + pamAuth(next, w, r) + }) + } +} + +func pamAuth(next http.Handler, w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok { + w.Header().Add("WWW-Authenticate", `Basic realm="Please provide your system credentials", charset="UTF-8"`) + http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized) + return + } + + t, err := pam.StartFunc("login", user, func(s pam.Style, msg string) (string, error) { + debug.Printf("%v %v", s, msg) + switch s { + case pam.PromptEchoOff: + return pass, nil + case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo: + return "", nil + default: + return "", fmt.Errorf("unsupported PAM conversation style: %v", s) + } + }) + if err != nil { + debug.Printf("Failed to start PAM conversation: %v", err) + http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable) + return + } + + if err := t.Authenticate(0); err != nil { + debug.Printf("Auth error: %v", err) + http.Error(w, "Invalid username or password", http.StatusUnauthorized) + return + } + + if err := t.AcctMgmt(0); err != nil { + debug.Printf("Account unavailable: %v", err) + http.Error(w, "Account unavailable", http.StatusUnauthorized) + return + } + + user, err = t.GetItem(pam.User) + if err != nil { + debug.Printf("Failed to get PAM username: %v", err) + http.Error(w, "Temporary authentication error, try again later", http.StatusServiceUnavailable) + return + } + + authCtx := AuthContext{ + AuthMethod: "pam", + UserName: user, + } + ctx := NewContext(r.Context(), &authCtx) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) +} diff --git a/auth/pam_stub.go b/auth/pam_stub.go new file mode 100644 index 0000000..c11a367 --- /dev/null +++ b/auth/pam_stub.go @@ -0,0 +1,11 @@ +//go:build !pam + +package auth + +import ( + "errors" +) + +func NewPAM() (AuthProvider, error) { + return nil, errors.New("PAM support is disabled") +} diff --git a/auth/url.go b/auth/url.go index 1f0fc0f..6c1a99f 100644 --- a/auth/url.go +++ b/auth/url.go @@ -16,6 +16,8 @@ func NewFromURL(authURL string) (AuthProvider, error) { return NewIMAP(u.Host, false), nil case "imaps": return NewIMAP(u.Host, true), nil + case "pam": + return NewPAM() default: return nil, fmt.Errorf("no auth provider found for %s:// URL", u.Scheme) } diff --git a/go.mod b/go.mod index e8c86aa..83db210 100644 --- a/go.mod +++ b/go.mod @@ -11,4 +11,7 @@ require ( github.com/go-chi/chi/v5 v5.0.7 ) -require golang.org/x/text v0.3.7 // indirect +require ( + github.com/msteinert/pam v1.0.0 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum index 8601ce7..9fe49cb 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/emersion/go-webdav v0.3.2-0.20220603063605-db966a275c93 h1:NNvUjFHONR github.com/emersion/go-webdav v0.3.2-0.20220603063605-db966a275c93/go.mod h1:uSM1VveeKtogBVWaYccTksToczooJ0rrVGNsgnDsr4Q= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/msteinert/pam v1.0.0 h1:4XoXKtMCH3+e6GIkW41uxm6B37eYqci/DH3gzSq7ocg= +github.com/msteinert/pam v1.0.0/go.mod h1:M4FPeAW8g2ITO68W8gACDz13NDJyOQM9IQsQhrR6TOI= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=