176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"encoding/xml"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/go-ldap/ldap"
|
|
"github.com/golang/glog"
|
|
)
|
|
|
|
type DirectoryEntry struct {
|
|
Name string
|
|
Telephone string
|
|
}
|
|
|
|
type CiscoIPPhoneDirectory struct {
|
|
Title string
|
|
Prompt string
|
|
DirectoryEntry []DirectoryEntry
|
|
}
|
|
|
|
var (
|
|
ldapAddress string
|
|
ldapUsername string
|
|
ldapPasswordFile string
|
|
ldapSearchBase string
|
|
ldapPhoneAttribute string
|
|
ldapNameAttribute string
|
|
bindAddress string
|
|
directoryTitle string
|
|
directoryPrompt string
|
|
regenerationPeriodString string
|
|
|
|
ldapPassword string
|
|
regenerationPeriod time.Duration
|
|
|
|
directory *CiscoIPPhoneDirectory
|
|
mutex sync.RWMutex
|
|
)
|
|
|
|
func main() {
|
|
flag.StringVar(&ldapAddress, "ldap_address", "ldap.hackerspace.pl", "Address of LDAP endpoint")
|
|
flag.StringVar(&ldapUsername, "ldap_username", "cn=ciscoaddressbook,ou=Services,dc=hackerspace,dc=pl", "DN to bind as to LDAP")
|
|
flag.StringVar(&ldapPasswordFile, "ldap_password_file", "/var/ciscoaddressbook/password", "File with password to bind with to LDAP.")
|
|
flag.StringVar(&ldapSearchBase, "ldap_search_base", "ou=People,dc=hackerspace,dc=pl", "LDAP search base.")
|
|
flag.StringVar(&ldapNameAttribute, "ldap_name_attribute", "uid", "LDAP attribute containing display name.")
|
|
flag.StringVar(&ldapPhoneAttribute, "ldap_phone_attribute", "mobile", "LDAP attribute containing phone number.")
|
|
flag.StringVar(&bindAddress, "bind_address", "127.0.0.1:8080", "Address to bind HTTP server to.")
|
|
flag.StringVar(&directoryTitle, "directory_title", "Warsaw Hackerspace Members", "Name of Phone Directory to display.")
|
|
flag.StringVar(&directoryPrompt, "directory_prompt", "Choose the Hacker", "Prompt of Phone Directory to display.")
|
|
flag.StringVar(®enerationPeriodString, "regeneration_rate", "1h", "How often to update the directory.")
|
|
flag.Parse()
|
|
|
|
file, err := os.Open(ldapPasswordFile)
|
|
if err != nil {
|
|
glog.Exit(err)
|
|
}
|
|
bytes, err := ioutil.ReadAll(file)
|
|
if err != nil {
|
|
glog.Exit(err)
|
|
}
|
|
ldapPassword = strings.TrimSpace(string(bytes))
|
|
|
|
period, err := time.ParseDuration(regenerationPeriodString)
|
|
if err != nil {
|
|
glog.Exit(err)
|
|
}
|
|
regenerationPeriod = period
|
|
|
|
if err = regenerateDirectory(); err != nil {
|
|
glog.Exit(err)
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
time.Sleep(regenerationPeriod)
|
|
if err := regenerateDirectory(); err != nil {
|
|
glog.Error("When regenerating directory: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
http.HandleFunc("/api/1/directory.xml", handleXMLRequest)
|
|
http.HandleFunc("/api/1/regenerate", handleRegenerateRequest)
|
|
glog.Exit(http.ListenAndServe(bindAddress, nil))
|
|
}
|
|
|
|
func handleXMLRequest(w http.ResponseWriter, r *http.Request) {
|
|
mutex.RLock()
|
|
defer mutex.RUnlock()
|
|
data, err := xml.Marshal(directory)
|
|
if err != nil {
|
|
glog.Error("When rendering directory: %v", err)
|
|
w.WriteHeader(500)
|
|
w.Write([]byte("An internal error occured."))
|
|
glog.Infof("%v: %v %v 500 Internal Error", r.RemoteAddr, r.Method, r.URL.RequestURI())
|
|
return
|
|
}
|
|
glog.Infof("%v: %v %v 200 OK", r.RemoteAddr, r.Method, r.URL.RequestURI())
|
|
w.Write(data)
|
|
}
|
|
|
|
func handleRegenerateRequest(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("Regeneration request queued."))
|
|
go func() {
|
|
err := regenerateDirectory()
|
|
if err != nil {
|
|
glog.Error("When regenerating directory: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func regenerateDirectory() error {
|
|
glog.Info("Regenerating directory...")
|
|
address := fmt.Sprintf("%s:389", ldapAddress)
|
|
l, err := ldap.Dial("tcp", address)
|
|
if err != nil {
|
|
return fmt.Errorf("in ldap.Dial(%q): %v", address, err)
|
|
}
|
|
config := &tls.Config{
|
|
ServerName: ldapAddress,
|
|
}
|
|
err = l.StartTLS(config)
|
|
if err != nil {
|
|
return fmt.Errorf("in StartTLS: %v", err)
|
|
}
|
|
err = l.Bind(ldapUsername, ldapPassword)
|
|
if err != nil {
|
|
return fmt.Errorf("in Bind: %v", err)
|
|
}
|
|
|
|
request := ldap.NewSearchRequest(
|
|
ldapSearchBase,
|
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
|
"(cn=*)",
|
|
[]string{"dn", ldapNameAttribute, ldapPhoneAttribute},
|
|
nil,
|
|
)
|
|
res, err := l.Search(request)
|
|
if err != nil {
|
|
glog.Exit(err)
|
|
}
|
|
|
|
mutex.Lock()
|
|
defer mutex.Unlock()
|
|
directory = &CiscoIPPhoneDirectory{
|
|
Title: directoryTitle,
|
|
Prompt: directoryPrompt,
|
|
DirectoryEntry: []DirectoryEntry{},
|
|
}
|
|
for _, entry := range res.Entries {
|
|
directoryEntry := DirectoryEntry{}
|
|
for _, attribute := range entry.Attributes {
|
|
if attribute.Name != ldapPhoneAttribute || len(attribute.Values) == 0 {
|
|
directoryEntry.Name = attribute.Values[0]
|
|
}
|
|
if attribute.Name != ldapNameAttribute || len(attribute.Values) == 0 {
|
|
directoryEntry.Telephone = attribute.Values[0]
|
|
}
|
|
}
|
|
if directoryEntry.Name == "" || directoryEntry.Telephone == "" {
|
|
continue
|
|
}
|
|
directory.DirectoryEntry = append(directory.DirectoryEntry, directoryEntry)
|
|
}
|
|
glog.Info("Directory regenerated.")
|
|
return nil
|
|
}
|