├── .gitignore ├── README.md ├── config_example.gcfg ├── ldap.go ├── main.go ├── main_test.go └── radius.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | config.gcfg 3 | ldap-radius 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ldap-radius 2 | 3 | A lightweight radius server written in go which uses ldap as authentication source. 4 | 5 | To configure you have to modify `config_example.gcfg` and copy it to `config.gcfg`. 6 | 7 | To build it, just run`go build` in the source directory. 8 | -------------------------------------------------------------------------------- /config_example.gcfg: -------------------------------------------------------------------------------- 1 | [radius] 2 | 3 | ; listen has to be in the format ip:port (or :port to just listen on all interfaces) 4 | listen = :1812 5 | 6 | ; your radius secret 7 | secret = GoblinsEveryWhere! 8 | 9 | [ldap] 10 | 11 | ; host in the form ip:port 12 | host = 172.16.0.2:389 13 | 14 | user = readonly 15 | password = secretpasswd 16 | 17 | basedn = lalala 18 | filter = (sAMAccountName={{username}}) 19 | 20 | secure = false 21 | -------------------------------------------------------------------------------- /ldap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "log" 6 | "strings" 7 | 8 | "gopkg.in/ldap.v2" 9 | ) 10 | 11 | var ldapConn *ldap.Conn 12 | 13 | func ldapLogin(username, password string) bool { 14 | log.Printf("[ldap] checking ldap password for %s in server %s\n", username, config.Ldap.Host) 15 | ldapConn, err := ldap.Dial("tcp", config.Ldap.Host) 16 | check(err, "could not connect to ldap server") 17 | defer ldapConn.Close() 18 | 19 | if config.Ldap.Secure { 20 | log.Printf("[ldap] initiating StartTLS\n") 21 | err = ldapConn.StartTLS(&tls.Config{InsecureSkipVerify: true}) 22 | check(err, "could not connect via tls") 23 | } 24 | 25 | err = ldapConn.Bind(config.Ldap.User, config.Ldap.Password) 26 | check(err, "could not bind using ldap lookup user") 27 | log.Printf("[ldap] successful bind to LDAP user with user %s\n", config.Ldap.User) 28 | 29 | log.Printf("[ldap] searching ldap tree with %s\n", strings.Replace(config.Ldap.Filter, "{{username}}", username, -1)) 30 | 31 | searchRequest := ldap.NewSearchRequest( 32 | config.Ldap.BaseDn, 33 | ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, 34 | strings.Replace(config.Ldap.Filter, "{{username}}", username, -1), 35 | []string{"dn"}, 36 | nil, 37 | ) 38 | 39 | sr, err := ldapConn.Search(searchRequest) 40 | check(err, "error searching a user") 41 | 42 | if len(sr.Entries) > 1 { 43 | log.Printf("[critical] more than 1 ldap entry found for %v. denying access!\n", username) 44 | } 45 | 46 | if len(sr.Entries) != 1 { 47 | return false 48 | } 49 | 50 | userdn := sr.Entries[0].DN 51 | 52 | err = ldapConn.Bind(userdn, password) 53 | if err != nil { 54 | log.Println(err) 55 | return false 56 | } 57 | 58 | return true 59 | } 60 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "gopkg.in/gcfg.v1" 10 | ) 11 | 12 | /*Config holds all values read from the config file*/ 13 | type Config struct { 14 | Radius struct { 15 | Listen string 16 | Secret string 17 | } 18 | Ldap struct { 19 | Host string 20 | User string 21 | Password string 22 | BaseDn string 23 | Filter string 24 | Secure bool 25 | } 26 | } 27 | 28 | var config Config 29 | 30 | func check(e error, msg string) { 31 | if e != nil { 32 | log.Println(msg) 33 | panic(e) 34 | } 35 | } 36 | 37 | func main() { 38 | err := gcfg.ReadFileInto(&config, "config.gcfg") 39 | check(err, "error reading config.gcfg") 40 | 41 | signalChan := make(chan os.Signal, 1) 42 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 43 | errChan := make(chan error) 44 | 45 | go func() { 46 | log.Println("[server] waiting for packets...") 47 | initRadius() 48 | err := radiusServer.ListenAndServe() 49 | if err != nil { 50 | errChan <- err 51 | } 52 | }() 53 | 54 | select { 55 | case <-signalChan: 56 | log.Println("[syscall] stopping server...") 57 | radiusServer.Stop() 58 | case err := <-errChan: 59 | log.Printf("[error] %v\n", err.Error()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "gopkg.in/gcfg.v1" 7 | ) 8 | 9 | func TestLogin(t *testing.T) { 10 | err := gcfg.ReadFileInto(&config, "config.gcfg") 11 | check(err, "error reading config.gcfg") 12 | if ldapLogin("lookup", "uplook") == false { 13 | t.Fail() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /radius.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/bronze1man/radius" 7 | ) 8 | 9 | type radiusService struct{} 10 | 11 | var radiusServer *radius.Server 12 | 13 | func (p radiusService) RadiusHandle(request *radius.Packet) *radius.Packet { 14 | log.Printf("[auth] New connection, %s for user %s\n", request.Code.String(), request.GetUsername()) 15 | npac := request.Reply() 16 | switch request.Code { 17 | 18 | case radius.AccessRequest: 19 | if ldapLogin(request.GetUsername(), request.GetPassword()) { 20 | log.Printf("[auth] Credentials OK\n") 21 | npac.Code = radius.AccessAccept 22 | return npac 23 | } 24 | log.Printf("[auth] Credentials are wrong, go away!\n") 25 | npac.Code = radius.AccessReject 26 | npac.AVPs = append(npac.AVPs, radius.AVP{Type: radius.ReplyMessage, Value: []byte("Go away!")}) 27 | return npac 28 | 29 | case radius.AccountingRequest: 30 | log.Printf("[acct] Accounting request, sending response\n") 31 | npac.Code = radius.AccountingResponse 32 | return npac 33 | 34 | default: 35 | log.Printf("[radius] Access rejected.\n") 36 | npac.Code = radius.AccessReject 37 | return npac 38 | } 39 | } 40 | 41 | func initRadius() { 42 | radiusServer = radius.NewServer(config.Radius.Listen, config.Radius.Secret, radiusService{}) 43 | /* or you can convert it to a server that accept request from some hosts with different secrets 44 | cls := radius.NewClientList([]radius.Client{ 45 | radius.NewClient("127.0.0.1", "secret1"), 46 | radius.NewClient("10.10.10.10", "secret2"), 47 | }) 48 | s.WithClientList(cls) 49 | */ 50 | } 51 | --------------------------------------------------------------------------------