auth: add PAM support
Handy for small local installations. Disabled by default because it adds a dependency on the PAM library.
This commit is contained in:
parent
228384530e
commit
10587f425b
@ -12,6 +12,7 @@ It currently provides:
|
|||||||
Authentication:
|
Authentication:
|
||||||
|
|
||||||
* IMAP (working)
|
* IMAP (working)
|
||||||
|
* PAM (working, enabled via the `pam` build tag)
|
||||||
|
|
||||||
Storage:
|
Storage:
|
||||||
|
|
||||||
|
79
auth/pam.go
Normal file
79
auth/pam.go
Normal file
@ -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)
|
||||||
|
}
|
11
auth/pam_stub.go
Normal file
11
auth/pam_stub.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
//go:build !pam
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPAM() (AuthProvider, error) {
|
||||||
|
return nil, errors.New("PAM support is disabled")
|
||||||
|
}
|
@ -16,6 +16,8 @@ func NewFromURL(authURL string) (AuthProvider, error) {
|
|||||||
return NewIMAP(u.Host, false), nil
|
return NewIMAP(u.Host, false), nil
|
||||||
case "imaps":
|
case "imaps":
|
||||||
return NewIMAP(u.Host, true), nil
|
return NewIMAP(u.Host, true), nil
|
||||||
|
case "pam":
|
||||||
|
return NewPAM()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("no auth provider found for %s:// URL", u.Scheme)
|
return nil, fmt.Errorf("no auth provider found for %s:// URL", u.Scheme)
|
||||||
}
|
}
|
||||||
|
5
go.mod
5
go.mod
@ -11,4 +11,7 @@ require (
|
|||||||
github.com/go-chi/chi/v5 v5.0.7
|
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
|
||||||
|
)
|
||||||
|
2
go.sum
2
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/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 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
|
||||||
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
Loading…
Reference in New Issue
Block a user