forked from drakkan/sftpgo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
135 lines (122 loc) · 3.45 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"strconv"
"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"
)
type userFilters struct {
DeniedLoginMethods []string `json:"denied_login_methods,omitempty"`
}
type minimalSFTPGoUser struct {
Status int `json:"status,omitempty"`
Username string `json:"username"`
HomeDir string `json:"home_dir,omitempty"`
UID int `json:"uid,omitempty"`
GID int `json:"gid,omitempty"`
Permissions map[string][]string `json:"permissions"`
Filters userFilters `json:"filters"`
}
func exitError() {
u := minimalSFTPGoUser{
Username: "",
}
json, _ := json.Marshal(u)
fmt.Printf("%v\n", string(json))
os.Exit(1)
}
func printSuccessResponse(username, homeDir string, uid, gid int) {
u := minimalSFTPGoUser{
Username: username,
HomeDir: homeDir,
UID: uid,
GID: gid,
Status: 1,
}
u.Permissions = make(map[string][]string)
u.Permissions["/"] = []string{"*"}
// uncomment the next line to require publickey+password authentication
//u.Filters.DeniedLoginMethods = []string{"publickey", "password", "keyboard-interactive", "publickey+keyboard-interactive"}
json, _ := json.Marshal(u)
fmt.Printf("%v\n", string(json))
os.Exit(0)
}
func main() {
// get credentials from env vars
username := os.Getenv("SFTPGO_AUTHD_USERNAME")
password := os.Getenv("SFTPGO_AUTHD_PASSWORD")
publickey := os.Getenv("SFTPGO_AUTHD_PUBLIC_KEY")
l, err := ldap.DialURL(bindURL)
if err != nil {
exitError()
}
defer l.Close()
// bind to the ldap server with an account that can read users
err = l.Bind(bindUsername, bindPassword)
if err != nil {
exitError()
}
// search the user trying to login and fetch some attributes, this search string is tested against 389ds using the default configuration
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=nsPerson)(uid=%s))", username),
[]string{"dn", "homeDirectory", "uidNumber", "gidNumber", "nsSshPublicKey"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
exitError()
}
// we expect exactly one user
if len(sr.Entries) != 1 {
exitError()
}
if len(publickey) > 0 {
// check public key
userKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publickey))
if err != nil {
exitError()
}
authOk := false
for _, k := range sr.Entries[0].GetAttributeValues("nsSshPublicKey") {
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 {
exitError()
}
} 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, password)
if err != nil {
exitError()
}
}
uid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("uidNumber"))
if err != nil {
exitError()
}
gid, err := strconv.Atoi(sr.Entries[0].GetAttributeValue("gidNumber"))
if err != nil {
exitError()
}
// return the authenticated user
printSuccessResponse(username, sr.Entries[0].GetAttributeValue("homeDirectory"), uid, gid)
}