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 }