commit 3b141d4f7eb700b7d58fa7a841a82dbbb58675dd Author: Sergiusz 'q3k' BazaƄski Date: Mon Jan 2 00:39:00 2017 +0000 Initial commit. diff --git a/main.go b/main.go new file mode 100644 index 0000000..d0b0923 --- /dev/null +++ b/main.go @@ -0,0 +1,176 @@ +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 +}