dba088daed
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
143 lines
4.6 KiB
Go
143 lines
4.6 KiB
Go
package httpd
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/drakkan/sftpgo/ldapauthserver/logger"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/render"
|
|
"github.com/go-ldap/ldap/v3"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func getSFTPGoUser(entry *ldap.Entry, username string) (SFTPGoUser, error) {
|
|
var err error
|
|
var user SFTPGoUser
|
|
uid := ldapConfig.DefaultUID
|
|
gid := ldapConfig.DefaultGID
|
|
status := 1
|
|
|
|
if !ldapConfig.ForceDefaultUID {
|
|
uid, err = strconv.Atoi(entry.GetAttributeValue(ldapConfig.GetUIDNumber()))
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
}
|
|
|
|
if !ldapConfig.ForceDefaultGID {
|
|
uid, err = strconv.Atoi(entry.GetAttributeValue(ldapConfig.GetGIDNumber()))
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
}
|
|
|
|
sftpgoUser := SFTPGoUser{
|
|
Username: username,
|
|
HomeDir: entry.GetAttributeValue(ldapConfig.GetHomeDirectory()),
|
|
UID: uid,
|
|
GID: gid,
|
|
Status: status,
|
|
}
|
|
sftpgoUser.Permissions = make(map[string][]string)
|
|
sftpgoUser.Permissions["/"] = []string{"*"}
|
|
return sftpgoUser, nil
|
|
}
|
|
|
|
func checkSFTPGoUserAuth(w http.ResponseWriter, r *http.Request) {
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
|
var authReq externalAuthRequest
|
|
err := render.DecodeJSON(r.Body, &authReq)
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "error decoding auth request: %v", err)
|
|
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
|
return
|
|
}
|
|
l, err := ldap.DialURL(ldapConfig.BindURL, ldap.DialWithTLSConfig(&tls.Config{
|
|
InsecureSkipVerify: ldapConfig.InsecureSkipVerify,
|
|
RootCAs: rootCAs,
|
|
}))
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "error connecting to the LDAP server: %v", err)
|
|
sendAPIResponse(w, r, err, "Error connecting to the LDAP server", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer l.Close()
|
|
|
|
err = l.Bind(ldapConfig.BindUsername, ldapConfig.BindPassword)
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "error binding to the LDAP server: %v", err)
|
|
sendAPIResponse(w, r, err, "Error binding to the LDAP server", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
searchRequest := ldap.NewSearchRequest(
|
|
ldapConfig.BaseDN,
|
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
|
strings.Replace(ldapConfig.SearchFilter, "%s", ldap.EscapeFilter(authReq.Username), 1),
|
|
ldapConfig.SearchBaseAttrs,
|
|
nil,
|
|
)
|
|
|
|
sr, err := l.Search(searchRequest)
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "error searching LDAP user %q: %v", authReq.Username, err)
|
|
sendAPIResponse(w, r, err, "Error searching LDAP user", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if len(sr.Entries) != 1 {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "expected one user, found: %v", len(sr.Entries))
|
|
sendAPIResponse(w, r, nil, fmt.Sprintf("Expected one user, found: %v", len(sr.Entries)), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
if len(authReq.PublicKey) > 0 {
|
|
userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(authReq.PublicKey))
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "invalid public key for user %q: %v", authReq.Username, err)
|
|
sendAPIResponse(w, r, err, "Invalid public key", http.StatusBadRequest)
|
|
return
|
|
}
|
|
authOk := false
|
|
for _, k := range sr.Entries[0].GetAttributeValues(ldapConfig.GetPublicKey()) {
|
|
key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k))
|
|
// we skip an invalid public key stored inside the LDAP server
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if bytes.Equal(key.Marshal(), userKey.Marshal()) {
|
|
authOk = true
|
|
break
|
|
}
|
|
}
|
|
if !authOk {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "public key authentication failed for user: %q", authReq.Username)
|
|
sendAPIResponse(w, r, nil, "public key authentication failed", http.StatusForbidden)
|
|
return
|
|
}
|
|
} else {
|
|
// bind to the LDAP server with the user dn and the given password to check the password
|
|
userdn := sr.Entries[0].DN
|
|
err = l.Bind(userdn, authReq.Password)
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "password authentication failed for user: %q", authReq.Username)
|
|
sendAPIResponse(w, r, nil, "password authentication failed", http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
|
|
user, err := getSFTPGoUser(sr.Entries[0], authReq.Username)
|
|
if err != nil {
|
|
logger.Warn(logSender, middleware.GetReqID(r.Context()), "get user from LDAP entry failed for username %q: %v",
|
|
authReq.Username, err)
|
|
sendAPIResponse(w, r, err, "mapping LDAP user failed", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
render.JSON(w, r, user)
|
|
}
|