Jelajahi Sumber

Flesh out examples/ldapauth, specifically:
Support 'virtual' users who have no homeDirectory, uidNumber or gidNumber.
Permit read-only access by a user named "anonymous", with any password.
Assume a conventional DIT with users under ou=people,dc=example,dc=com.
Read the LDAP bindPassword from a file (not baked into the code).
Log progress and problems to syslog.

root 3 tahun lalu
induk
melakukan
87751e562e
1 mengubah file dengan 54 tambahan dan 14 penghapusan
  1. 54 14
      examples/ldapauth/main.go

+ 54 - 14
examples/ldapauth/main.go

@@ -4,17 +4,22 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"log"
+	"log/syslog"
 	"os"
 	"strconv"
+	"strings"
 
 	"github.com/go-ldap/ldap/v3"
 	"golang.org/x/crypto/ssh"
 )
 
 const (
-	bindUsername = "cn=Directory Manager"
-	bindPassword = "YOUR_ADMIN_PASSWORD_HERE"
-	bindURL      = "ldap://192.168.1.103:389"
+	rootDN       = "dc=example,dc=com"
+	bindUsername = "cn=sftpgo," + rootDN
+	bindURL      = "ldap:///" // That is, the server on the default port of localhost.
+	passwordFile = "/etc/sftpgo/admin-password.txt" // make this file readable only by the server
+	publicDir    = "/var/www/webdav/public"
 )
 
 type userFilters struct {
@@ -32,6 +37,7 @@ type minimalSFTPGoUser struct {
 }
 
 func exitError() {
+	log.Printf("exitError\n")
 	u := minimalSFTPGoUser{
 		Username: "",
 	}
@@ -40,7 +46,7 @@ func exitError() {
 	os.Exit(1)
 }
 
-func printSuccessResponse(username, homeDir string, uid, gid int) {
+func printSuccessResponse(username, homeDir string, uid, gid int, permissions []string) {
 	u := minimalSFTPGoUser{
 		Username: username,
 		HomeDir:  homeDir,
@@ -49,46 +55,66 @@ func printSuccessResponse(username, homeDir string, uid, gid int) {
 		Status:   1,
 	}
 	u.Permissions = make(map[string][]string)
-	u.Permissions["/"] = []string{"*"}
+	u.Permissions["/"] = permissions
 	// uncomment the next line to require publickey+password authentication
 	//u.Filters.DeniedLoginMethods = []string{"publickey", "password", "keyboard-interactive", "publickey+keyboard-interactive"}
 	resp, _ := json.Marshal(u)
+	log.Printf("%v\n", string(resp))
 	fmt.Printf("%v\n", string(resp))
 	os.Exit(0)
 }
 
 func main() {
+	logWriter, err := syslog.New(syslog.LOG_NOTICE, "sftpgo")
+	if err == nil {
+		log.SetOutput(logWriter)
+	}
 	// get credentials from env vars
 	username := os.Getenv("SFTPGO_AUTHD_USERNAME")
 	password := os.Getenv("SFTPGO_AUTHD_PASSWORD")
 	publickey := os.Getenv("SFTPGO_AUTHD_PUBLIC_KEY")
+	if strings.ToLower(username) == "anonymous" {
+		printSuccessResponse("anonymous", publicDir, 0, 0, []string{"list", "download"})
+		return
+	}
 	l, err := ldap.DialURL(bindURL)
 	if err != nil {
+		log.Printf("DialURL: %s\n", err.Error())
 		exitError()
 	}
 	defer l.Close()
 	// bind to the ldap server with an account that can read users
-	err = l.Bind(bindUsername, bindPassword)
+	bindPassword, err := os.ReadFile(passwordFile)
+	if err != nil {
+		log.Printf("ReadFile(%s): %s\n", passwordFile, err.Error())
+		exitError()
+	}
+	err = l.Bind(bindUsername, string(bindPassword))
 	if err != nil {
+		log.Printf("Bind(%s): %s\n", bindUsername, err.Error())
 		exitError()
 	}
 
 	// search the user trying to login and fetch some attributes, this search string is tested against 389ds using the default configuration
+	log.Printf("username=%s\n", username)
+	searchFilter := fmt.Sprintf("(uid=%s)", username)
 	searchRequest := ldap.NewSearchRequest(
-		"dc=example,dc=com",
+		"ou=people," + rootDN,
 		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
-		fmt.Sprintf("(&(objectClass=nsPerson)(uid=%s))", username),
-		[]string{"dn", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"},
+		searchFilter,
+		[]string{"dn", "uid", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"},
 		nil,
 	)
 
 	sr, err := l.Search(searchRequest)
 	if err != nil {
+		log.Printf("Search(%s): %s\n", searchFilter, err.Error())
 		exitError()
 	}
 
 	// we expect exactly one user
 	if len(sr.Entries) != 1 {
+		log.Printf("Search(%s): %d entries\n", searchFilter, len(sr.Entries))
 		exitError()
 	}
 
@@ -96,6 +122,7 @@ func main() {
 		// check public key
 		userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publickey))
 		if err != nil {
+			log.Printf("ParseAuthorizedKey(%s): %s\n", publickey, err.Error())
 			exitError()
 		}
 		authOk := false
@@ -111,25 +138,38 @@ func main() {
 			}
 		}
 		if !authOk {
+			log.Printf("publickey %s !authOk\n", publickey)
 			exitError()
 		}
 	} else {
 		// bind to the LDAP server with the user dn and the given password to check the password
 		userdn := sr.Entries[0].DN
+		// log.Printf("password=%s\n", password)
 		err = l.Bind(userdn, password)
 		if err != nil {
+			log.Printf("Bind(%s): %s\n", userdn, err.Error())
 			exitError()
 		}
 	}
 
-	uid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("uidNumber"))
+	// People in the LDAP directory aren't necessarily Linux users;
+	// so they might not have a uidNumber or gidNumber.
+	uidNumber := sr.Entries[0].GetAttributeValue("uidNumber")
+	uid, err := strconv.Atoi(uidNumber)
 	if err != nil {
-		exitError()
+		//log.Printf("uid Atoi(%s) = %s\n", uidNumber, err.Error())
+		uid = 0
 	}
-	gid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("gidNumber"))
+	gidNumber := sr.Entries[0].GetAttributeValue("gidNumber")
+	gid, err := strconv.Atoi(gidNumber)
 	if err != nil {
-		exitError()
+		//log.Printf("gid Atoi(%s) = %s\n", gidNumber, err.Error())
+		gid = 0
+	}
+	homeDir := sr.Entries[0].GetAttributeValue("homeDirectory")
+	if (len(homeDir) <= 0) {
+		homeDir = publicDir // homeDir is a required attribute.
 	}
 	// return the authenticated user
-	printSuccessResponse(username, sr.Entries[0].GetAttributeValue("homeDirectory"), uid, gid)
+	printSuccessResponse(sr.Entries[0].GetAttributeValue("uid"), homeDir, uid, gid, []string{"*"})
 }