aboutsummaryrefslogblamecommitdiff
path: root/main.go
blob: 30fd42e83ebdba53b3a9f8baab9f21d79b3efe9a (plain) (tree)
1
2
3
4
5
6
7


            
                     

                       
                       




                   
 
                                    
                                
                                     

 

                                                     
                                                       
                                               
 








                                                                           
 



                                                                 
 





                                                  
                                                    

                                                       
 

                                                         
 




                                                 
 
 

                                                                                      
 


                                      
 


                                


                              
                                                          

                                        
                                                       
 
                                     

                                     
                                         
                                               

         
                                      
                               
                                                                                                                                                                                                          
















                                                  

 
                                                  





                                                                                

 
             





                                   






                                                                 

         
 


                                              

                                   






                                                    
 

                                     
                                           
                                             
 

                                                                               
 

                                               
                                                              
 
                                                                
                                                   
 



                                                                       
                                                                   
 


                                                      

                                                                   
                                                                
 

                                                        

                                                                   
                                                         
                                                                              
 
                                                            
                                                                                
 
                                                                     
                                                                     



                                                            
 






                                                                              

                     
                      
                          
                          
                     

 
                                                         
                                                
 

                                     


                      
                              
                             
                                      


                                     

 
                        


                                                           
                                                       











                                                                          
                                                       

 
                           

                           

                         


                           
                                                          
                                                  
 

                                                         
                      



                                                                
                                                                
                                                          
 



                                                                                  

                 
                                                                
                               










                                                                                          
                              




                                                             
                                                               

                 

                                                           



                                                                                  
                              

                 
                                                          

                                                                          
         
 
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)
	}
}