package main import ( "crypto/rand" "encoding/json" "flag" "html/template" "io/ioutil" "log" "net/http" "os" "strings" "github.com/go-ldap/ldap/v3" "github.com/gorilla/mux" "github.com/gorilla/sessions" ) type ConfigFile struct { HttpBindAddr string `json:"http_bind_addr"` LdapServerAddr string `json:"ldap_server_addr"` LdapTLS bool `json:"ldap_tls"` BaseDN string `json:"base_dn"` UserBaseDN string `json:"user_base_dn"` UserNameAttr string `json:"user_name_attr"` GroupBaseDN string `json:"group_base_dn"` GroupNameAttr string `json:"group_name_attr"` MailingBaseDN string `json:"mailing_list_base_dn"` MailingNameAttr string `json:"mailing_list_name_attr"` MailingGuestsBaseDN string `json:"mailing_list_guest_user_base_dn"` InvitationBaseDN string `json:"invitation_base_dn"` InvitationNameAttr string `json:"invitation_name_attr"` InvitedMailFormat string `json:"invited_mail_format"` InvitedAutoGroups []string `json:"invited_auto_groups"` WebAddress string `json:"web_address"` MailFrom string `json:"mail_from"` SMTPServer string `json:"smtp_server"` SMTPUsername string `json:"smtp_username"` SMTPPassword string `json:"smtp_password"` AdminAccount string `json:"admin_account"` GroupCanInvite string `json:"group_can_invite"` GroupCanAdmin string `json:"group_can_admin"` S3AdminEndpoint string `json:"s3_admin_endpoint"` S3AdminToken string `json:"s3_admin_token"` S3Endpoint string `json:"s3_endpoint"` S3AccessKey string `json:"s3_access_key"` S3SecretKey string `json:"s3_secret_key"` S3Region string `json:"s3_region"` S3Bucket string `json:"s3_bucket"` } var fsServer = flag.NewFlagSet("server", flag.ContinueOnError) var configFlag = fsServer.String("config", "./config.json", "Configuration file path") var config *ConfigFile const SESSION_NAME = "guichet_session" var staticPath = "./static" var templatePath = "./templates" var store sessions.Store = nil func readConfig() ConfigFile { // Default configuration values for certain fields config_file := ConfigFile{ HttpBindAddr: ":9991", LdapServerAddr: "ldap://127.0.0.1:389", UserNameAttr: "uid", GroupNameAttr: "gid", InvitationNameAttr: "cn", InvitedAutoGroups: []string{}, } _, err := os.Stat(*configFlag) if os.IsNotExist(err) { log.Fatalf("Could not find Guichet configuration file at %s. Please create this file, for example starting with config.json.example and customizing it for your deployment.", *configFlag) } if err != nil { log.Fatal(err) } bytes, err := ioutil.ReadFile(*configFlag) if err != nil { log.Fatal(err) } err = json.Unmarshal(bytes, &config_file) if err != nil { log.Fatal(err) } return config_file } func getTemplate(name string) *template.Template { return template.Must(template.New("layout.html").Funcs(template.FuncMap{ "contains": strings.Contains, }).ParseFiles( templatePath+"/layout.html", templatePath+"/"+name, )) } func main() { if len(os.Args) < 2 { server(os.Args[1:]) return } switch os.Args[1] { case "cli": cliMain(os.Args[2:]) case "server": server(os.Args[2:]) default: log.Println("Usage: guichet [server|cli] --help") os.Exit(1) } } func server(args []string) { log.Println("Starting Guichet Server") fsServer.Parse(args) config_file := readConfig() config = &config_file session_key := make([]byte, 32) n, err := rand.Read(session_key) if err != nil || n != 32 { log.Fatal(err) } store = sessions.NewCookieStore(session_key) r := mux.NewRouter() r.HandleFunc("/", handleHome) r.HandleFunc("/login", handleLogin) r.HandleFunc("/logout", handleLogout) r.HandleFunc("/api/unstable/website", handleAPIWebsiteList) r.HandleFunc("/api/unstable/website/{bucket}", handleAPIWebsiteInspect) r.HandleFunc("/profile", handleProfile) r.HandleFunc("/passwd", handlePasswd) r.HandleFunc("/picture/{name}", handleDownloadPicture) r.HandleFunc("/directory/search", handleDirectorySearch) r.HandleFunc("/directory", handleDirectory) r.HandleFunc("/website", handleWebsiteList) r.HandleFunc("/website/new", handleWebsiteNew) r.HandleFunc("/website/configure", handleWebsiteConfigure) r.HandleFunc("/website/inspect/{bucket}", handleWebsiteInspect) r.HandleFunc("/website/vhost/{bucket}", handleWebsiteVhost) r.HandleFunc("/pim/setup", handlePimSetup) r.HandleFunc("/pim/inspect", handlePimInspect) r.HandleFunc("/invite/new_account", handleInviteNewAccount) r.HandleFunc("/invite/send_code", handleInviteSendCode) r.HandleFunc("/invitation/{code}", handleInvitationCode) r.HandleFunc("/admin/users", handleAdminUsers) r.HandleFunc("/admin/groups", handleAdminGroups) r.HandleFunc("/admin/mailing", handleAdminMailing) r.HandleFunc("/admin/mailing/{id}", handleAdminMailingList) r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP) r.HandleFunc("/admin/create/{template}/{super_dn}", handleAdminCreate) staticfiles := http.FileServer(http.Dir(staticPath)) r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticfiles)) log.Printf("Starting HTTP server on %s", config.HttpBindAddr) err = http.ListenAndServe(config.HttpBindAddr, logRequest(r)) if err != nil { log.Fatal("Cannot start http server: ", err) } } func logRequest(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL) handler.ServeHTTP(w, r) }) } // Page handlers ---- // --- Home Controller type HomePageData struct { User *LoggedUser BaseDN string } func handleHome(w http.ResponseWriter, r *http.Request) { templateHome := getTemplate("home.html") user := RequireUserHtml(w, r) if user == nil { return } data := &HomePageData{ User: user, BaseDN: config.BaseDN, } templateHome.Execute(w, data) } // --- Logout Controller func handleLogout(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, SESSION_NAME) if err != nil { session, _ = store.New(r, SESSION_NAME) } delete(session.Values, "login_username") delete(session.Values, "login_password") delete(session.Values, "login_dn") err = session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/login", http.StatusFound) } // --- Login Controller --- type LoginFormData struct { Username string WrongUser bool WrongPass bool ErrorMessage string } func handleLogin(w http.ResponseWriter, r *http.Request) { templateLogin := getTemplate("login.html") if r.Method == "GET" { templateLogin.Execute(w, LoginFormData{}) return } else if r.Method == "POST" { r.ParseForm() username := strings.Join(r.Form["username"], "") password := strings.Join(r.Form["password"], "") loginInfo := LoginInfo{username, password} l, err := NewLdapCon() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } err = l.Bind(loginInfo.DN(), loginInfo.Password) if err != nil { data := &LoginFormData{ Username: username, } if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) { data.WrongPass = true } else if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { data.WrongUser = true } else { data.ErrorMessage = err.Error() } templateLogin.Execute(w, data) return } // Successfully logged in, save it to session session, err := store.Get(r, SESSION_NAME) if err != nil { session, _ = store.New(r, SESSION_NAME) } session.Values["login_username"] = username session.Values["login_password"] = password err = session.Save(r, w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, "/", http.StatusFound) } else { http.Error(w, "Unsupported method", http.StatusBadRequest) } }