Exemple #1
0
func (users *Users) createHandler(mode string, runtime phoenix.Runtime) (handler UsersHandler, err error) {

	switch mode {
	case "sharedsecret":
		secret, _ := runtime.GetString("users", "sharedsecret_secret")
		if secret != "" {
			handler = &UsersSharedsecretHandler{secret: []byte(secret)}
		} else {
			err = errors.New("Cannot enable sharedsecret users handler: No secret.")
		}
	case "httpheader":
		headerName, _ := runtime.GetString("users", "httpheader_header")
		if headerName == "" {
			headerName = "x-users"
		}
		handler = &UsersHTTPHeaderHandler{headerName: headerName}
	case "certificate":
		var err2 error
		verifiedHeader, _ := runtime.GetString("users", "certificate_verifiedHeader")
		verifiedHeaderValue, _ := runtime.GetString("users", "certificate_verifiedHeaderValue")
		certificateHeader, _ := runtime.GetString("users", "certificate_certificateHeader")
		validForDays, _ := runtime.GetInt("users", "certificate_validForDays")
		if validForDays == 0 {
			validForDays = 365
		}
		organization, _ := runtime.GetString("users", "certificate_organization")
		if organization == "" {
			organization = "My Spreed Server"
		}
		uh := &UsersCertificateHandler{
			verifiedHeader:      verifiedHeader,
			verifiedHeaderValue: verifiedHeaderValue,
			certificateHeader:   certificateHeader,
			validFor:            time.Duration(validForDays) * 24 * time.Hour,
			organization:        []string{organization},
		}
		keyFn, _ := runtime.GetString("users", "certificate_key")
		certificateFn, _ := runtime.GetString("users", "certificate_certificate")
		if keyFn != "" && certificateFn != "" {
			// Load private key from file and use it for signing,
			if uh.privateKey, err2 = loadX509PrivateKey(keyFn); err2 == nil {
				log.Printf("Users certificate private key loaded from %s\n", keyFn)
			} else {
				log.Printf("Failed to load certificate private key: %s\n", err2)
			}
		}
		if certificateFn != "" {
			// Load Certificate from file.
			var certificate tls.Certificate
			if certificate, err = loadX509Certificate(certificateFn); err == nil {
				// Parse first certificate in file.
				var certificates []*x509.Certificate
				if certificates, err = x509.ParseCertificates(certificate.Certificate[0]); err == nil {
					// Use first parsed certificate as CA.
					uh.certificate = certificates[0]
					log.Printf("Users certificate loaded from %s\n", certificateFn)
					handler = uh
					// Get TLS config if the server has one.
					if tlsConfig, err2 := runtime.TLSConfig(); err2 == nil {
						// Enable TLS client certificate authentication.
						tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
						// Create cert pool.
						pool := x509.NewCertPool()
						// Add CA certificate to pool for TLS client authentication.
						for _, derCert := range certificate.Certificate {
							cert, err2 := x509.ParseCertificate(derCert)
							if err2 != nil {
								continue
							}
							pool.AddCert(cert)
						}
						// Add pool to config.
						tlsConfig.ClientCAs = pool
						log.Printf("Initialized TLS auth pool with %d certificates.", len(pool.Subjects()))
					}
				}
			}
		} else {
			err = errors.New("Cannot enable certificate users handler: No certificate.")
		}
	}

	return

}
Exemple #2
0
func runner(runtime phoenix.Runtime) error {

	log.SetFlags(log.LstdFlags | log.Lmicroseconds)

	rootFolder, err := runtime.GetString("http", "root")
	if err != nil {
		cwd, err2 := os.Getwd()
		if err2 != nil {
			return fmt.Errorf("Error while getting current directory: %s", err)
		}
		rootFolder = cwd
	}

	if !httputils.HasDirPath(rootFolder) {
		return fmt.Errorf("Configured root '%s' is not a directory.", rootFolder)
	}

	if !httputils.HasFilePath(path.Join(rootFolder, "static", "css", "main.min.css")) {
		return fmt.Errorf("Unable to find client. Path correct and compiled css?")
	}

	statsEnabled, err := runtime.GetBool("http", "stats")
	if err != nil {
		statsEnabled = false
	}

	pprofListen, err := runtime.GetString("http", "pprofListen")
	if err == nil && pprofListen != "" {
		log.Printf("Starting pprof HTTP server on %s", pprofListen)
		go func() {
			log.Println(http.ListenAndServe(pprofListen, nil))
		}()
	}

	var sessionSecret []byte
	sessionSecretString, err := runtime.GetString("app", "sessionSecret")
	if err != nil {
		return fmt.Errorf("No sessionSecret in config file.")
	}
	sessionSecret, err = hex.DecodeString(sessionSecretString)
	if err != nil {
		log.Println("Warning: sessionSecret value is not a hex encoded", err)
		sessionSecret = []byte(sessionSecretString)
	}
	if len(sessionSecret) < 32 {
		return fmt.Errorf("Length of sessionSecret must be at least 32 bytes.")
	}

	if len(sessionSecret) < 32 {
		log.Printf("Weak sessionSecret (only %d bytes). It is recommended to use a key with 32 or 64 bytes.\n", len(sessionSecret))
	}

	var encryptionSecret []byte
	encryptionSecretString, err := runtime.GetString("app", "encryptionSecret")
	if err != nil {
		return fmt.Errorf("No encryptionSecret in config file.")
	}
	encryptionSecret, err = hex.DecodeString(encryptionSecretString)
	if err != nil {
		log.Println("Warning: encryptionSecret value is not a hex encoded", err)
		encryptionSecret = []byte(encryptionSecretString)
	}
	switch l := len(encryptionSecret); {
	case l == 16:
	case l == 24:
	case l == 32:
	default:
		return fmt.Errorf("Length of encryptionSecret must be exactly 16, 24 or 32 bytes to select AES-128, AES-192 or AES-256.")
	}

	var turnSecret []byte
	turnSecretString, err := runtime.GetString("app", "turnSecret")
	if err == nil {
		turnSecret = []byte(turnSecretString)
	}

	serverRealm, err := runtime.GetString("app", "serverRealm")
	if err != nil {
		serverRealm = "local"
	}

	// Create token provider.
	tokenFile, err := runtime.GetString("app", "tokenFile")
	if err == nil {
		if !httputils.HasFilePath(path.Clean(tokenFile)) {
			return fmt.Errorf("Unable to find token file at %s", tokenFile)
		}
	}

	var tokenProvider TokenProvider
	if tokenFile != "" {
		log.Printf("Using token authorization from %s\n", tokenFile)
		tokenProvider = TokenFileProvider(tokenFile)
	}

	// Load remaining configuration items.
	config = NewConfig(runtime, tokenProvider != nil)

	// Load templates.
	templates = template.New("")
	templates.Delims("<%", "%>")

	// Load html templates folder
	err = filepath.Walk(path.Join(rootFolder, "html"), func(path string, info os.FileInfo, err error) error {
		if err == nil {
			if strings.HasSuffix(path, ".html") {
				_, err = templates.ParseFiles(path)
				if err != nil {
					return err
				}
			}
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("Failed to load templates: %s", err)
	}

	// Load extra templates folder
	extraFolder, err := runtime.GetString("app", "extra")
	if err == nil {
		if !httputils.HasDirPath(extraFolder) {
			return fmt.Errorf("Configured extra '%s' is not a directory.", extraFolder)
		}
		templates, err = templates.ParseGlob(path.Join(extraFolder, "*.html"))
		if err != nil {
			return fmt.Errorf("Failed to load extra templates: %s", err)
		}
		log.Printf("Loaded extra templates from: %s", extraFolder)
	}

	// Define incoming channeling API limit it byte. Larger messages will be discarded.
	incomingCodecLimit := 1024 * 1024 // 1MB

	// Create realm string from config.
	computedRealm := fmt.Sprintf("%s.%s", serverRealm, config.Token)

	// Set number of go routines if it is 1
	if goruntime.GOMAXPROCS(0) == 1 {
		nCPU := goruntime.NumCPU()
		goruntime.GOMAXPROCS(nCPU)
		log.Printf("Using the number of CPU's (%d) as GOMAXPROCS\n", nCPU)
	}

	// Get current number of max open files.
	var rLimit syscall.Rlimit
	err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
	if err != nil {
		log.Println("Error getting max numer of open files", err)
	} else {
		log.Printf("Max open files are %d\n", rLimit.Max)
	}

	// Try to increase number of file open files. This only works as root.
	maxfd, err := runtime.GetInt("http", "maxfd")
	if err == nil {
		rLimit.Max = uint64(maxfd)
		rLimit.Cur = uint64(maxfd)
		err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
		if err != nil {
			log.Println("Error setting max open files", err)
		} else {
			log.Printf("Set max open files successfully to %d\n", uint64(maxfd))
		}
	}

	// Create router.
	router := mux.NewRouter()
	r := router.PathPrefix(config.B).Subrouter().StrictSlash(true)

	// HTTP listener support.
	if _, err = runtime.GetString("http", "listen"); err == nil {
		runtime.DefaultHTTPHandler(r)
	}

	// Native HTTPS listener support.
	if _, err = runtime.GetString("https", "listen"); err == nil {
		// Setup TLS.
		tlsConfig, err := runtime.TLSConfig()
		if err != nil {
			return fmt.Errorf("TLS configuration error: %s", err)
		}
		// Explicitly set random to use.
		tlsConfig.Rand = rand.Reader
		log.Println("Native TLS configuration intialized")
		runtime.DefaultHTTPSHandler(r)
	}

	// Prepare services.
	buddyImages := NewImageCache()
	codec := NewCodec(incomingCodecLimit)
	roomManager := NewRoomManager(config, codec)
	hub := NewHub(config, sessionSecret, encryptionSecret, turnSecret, codec)
	tickets := NewTickets(sessionSecret, encryptionSecret, computedRealm)
	sessionManager := NewSessionManager(config, tickets, hub, roomManager, roomManager, buddyImages, sessionSecret)
	statsManager := NewStatsManager(hub, roomManager, sessionManager)
	channellingAPI := NewChannellingAPI(config, roomManager, tickets, sessionManager, statsManager, hub, hub, hub)

	// Add handlers.
	r.HandleFunc("/", httputils.MakeGzipHandler(mainHandler))
	r.Handle("/static/img/buddy/{flags}/{imageid}/{idx:.*}", http.StripPrefix(config.B, makeImageHandler(buddyImages, time.Duration(24)*time.Hour)))
	r.Handle("/static/{path:.*}", http.StripPrefix(config.B, httputils.FileStaticServer(http.Dir(rootFolder))))
	r.Handle("/robots.txt", http.StripPrefix(config.B, http.FileServer(http.Dir(path.Join(rootFolder, "static")))))
	r.Handle("/favicon.ico", http.StripPrefix(config.B, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img")))))
	r.Handle("/ws", makeWSHandler(statsManager, sessionManager, codec, channellingAPI))

	// Simple room handler.
	r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler))

	// Sandbox handler.
	r.HandleFunc("/sandbox/{origin_scheme}/{origin_host}/{sandbox}.html", httputils.MakeGzipHandler(sandboxHandler))

	// Add API end points.
	api := sloth.NewAPI()
	api.SetMux(r.PathPrefix("/api/v1/").Subrouter())
	api.AddResource(&Rooms{}, "/rooms")
	api.AddResource(config, "/config")
	api.AddResourceWithWrapper(&Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens")
	if config.UsersEnabled {
		// Create Users handler.
		users := NewUsers(hub, tickets, sessionManager, config.UsersMode, serverRealm, runtime)
		api.AddResource(&Sessions{tickets, hub, users}, "/sessions/{id}/")
		if config.UsersAllowRegistration {
			api.AddResource(users, "/users")
		}
	}
	if statsEnabled {
		api.AddResourceWithWrapper(&Stats{statsManager}, httputils.MakeGzipHandler, "/stats")
		log.Println("Stats are enabled!")
	}

	// Add extra/static support if configured and exists.
	if extraFolder != "" {
		extraFolderStatic := path.Join(extraFolder, "static")
		if _, err = os.Stat(extraFolderStatic); err == nil {
			r.Handle("/extra/static/{path:.*}", http.StripPrefix(fmt.Sprintf("%sextra", config.B), httputils.FileStaticServer(http.Dir(extraFolder))))
			log.Printf("Added URL handler /extra/static/... for static files in %s/...\n", extraFolderStatic)
		}
	}

	// Map everything else to a room when it is a GET.
	rooms := r.PathPrefix("/").Methods("GET").Subrouter()
	rooms.HandleFunc("/{room:.*}", httputils.MakeGzipHandler(roomHandler))

	return runtime.Start()
}