2015-05-01 20:59:46 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2017-04-15 02:48:24 +00:00
|
|
|
"crypto/tls"
|
2015-05-01 20:59:46 +00:00
|
|
|
"log"
|
2016-01-04 18:26:32 +00:00
|
|
|
"net"
|
2015-05-01 20:59:46 +00:00
|
|
|
"net/http"
|
2016-01-04 18:26:32 +00:00
|
|
|
"net/url"
|
|
|
|
"strings"
|
2018-12-11 09:51:20 +00:00
|
|
|
"sync"
|
2018-12-16 11:19:16 +00:00
|
|
|
"time"
|
2015-05-01 20:59:46 +00:00
|
|
|
|
2016-03-01 00:51:26 +00:00
|
|
|
"github.com/gorilla/websocket"
|
2018-12-11 09:51:20 +00:00
|
|
|
"github.com/khlieng/dispatch/config"
|
2018-12-17 11:45:33 +00:00
|
|
|
"github.com/khlieng/dispatch/pkg/netutil"
|
2018-05-31 21:24:59 +00:00
|
|
|
"github.com/khlieng/dispatch/pkg/session"
|
2015-12-11 03:35:48 +00:00
|
|
|
"github.com/khlieng/dispatch/storage"
|
2018-12-16 11:19:16 +00:00
|
|
|
"github.com/mholt/certmagic"
|
2015-05-01 20:59:46 +00:00
|
|
|
)
|
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
var channelStore = storage.NewChannelStore()
|
2016-01-15 01:27:30 +00:00
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
type Dispatch struct {
|
|
|
|
Store storage.Store
|
|
|
|
SessionStore storage.SessionStore
|
|
|
|
|
|
|
|
GetMessageStore func(*storage.User) (storage.MessageStore, error)
|
|
|
|
GetMessageSearchProvider func(*storage.User) (storage.MessageSearchProvider, error)
|
|
|
|
|
2018-12-11 09:51:20 +00:00
|
|
|
cfg *config.Config
|
2018-05-31 21:24:59 +00:00
|
|
|
upgrader websocket.Upgrader
|
|
|
|
states *stateStore
|
2018-12-11 09:51:20 +00:00
|
|
|
lock sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(cfg *config.Config) *Dispatch {
|
|
|
|
return &Dispatch{
|
|
|
|
cfg: cfg,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dispatch) Config() *config.Config {
|
|
|
|
d.lock.Lock()
|
|
|
|
cfg := d.cfg
|
|
|
|
d.lock.Unlock()
|
|
|
|
return cfg
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dispatch) SetConfig(cfg *config.Config) {
|
|
|
|
d.lock.Lock()
|
|
|
|
d.cfg = cfg
|
|
|
|
d.lock.Unlock()
|
2018-05-31 21:24:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dispatch) Run() {
|
|
|
|
d.upgrader = websocket.Upgrader{
|
2015-05-01 22:20:22 +00:00
|
|
|
ReadBufferSize: 1024,
|
|
|
|
WriteBufferSize: 1024,
|
|
|
|
}
|
2015-05-01 20:59:46 +00:00
|
|
|
|
2018-12-11 09:51:20 +00:00
|
|
|
if d.Config().Dev {
|
2018-05-31 21:24:59 +00:00
|
|
|
d.upgrader.CheckOrigin = func(r *http.Request) bool {
|
2016-01-27 17:03:38 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
session.CookieName = "dispatch"
|
|
|
|
|
|
|
|
d.states = newStateStore(d.SessionStore)
|
2018-06-01 03:40:12 +00:00
|
|
|
go d.states.run()
|
2018-05-31 21:24:59 +00:00
|
|
|
|
|
|
|
d.loadUsers()
|
|
|
|
d.initFileServer()
|
|
|
|
d.startHTTP()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dispatch) loadUsers() {
|
|
|
|
users, err := storage.LoadUsers(d.Store)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-06-01 03:40:12 +00:00
|
|
|
log.Printf("[Init] %d users", len(users))
|
2018-05-31 21:24:59 +00:00
|
|
|
|
2018-06-01 02:16:38 +00:00
|
|
|
for _, user := range users {
|
|
|
|
go d.loadUser(user)
|
2018-05-31 21:24:59 +00:00
|
|
|
}
|
2015-06-06 23:18:26 +00:00
|
|
|
}
|
2015-05-01 20:59:46 +00:00
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
func (d *Dispatch) loadUser(user *storage.User) {
|
|
|
|
messageStore, err := d.GetMessageStore(user)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
user.SetMessageStore(messageStore)
|
|
|
|
|
|
|
|
search, err := d.GetMessageSearchProvider(user)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
user.SetMessageSearchProvider(search)
|
|
|
|
|
|
|
|
state := NewState(user, d)
|
|
|
|
d.states.set(state)
|
|
|
|
go state.run()
|
|
|
|
|
|
|
|
channels, err := user.GetChannels()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
servers, err := user.GetServers()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, server := range servers {
|
2018-08-10 18:24:29 +00:00
|
|
|
i := connectIRC(server, state, user.GetLastIP())
|
2018-05-31 21:24:59 +00:00
|
|
|
|
|
|
|
var joining []string
|
|
|
|
for _, channel := range channels {
|
|
|
|
if channel.Server == server.Host {
|
|
|
|
joining = append(joining, channel.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
i.Join(joining...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Dispatch) startHTTP() {
|
2018-12-11 09:51:20 +00:00
|
|
|
cfg := d.Config()
|
2018-12-16 11:19:16 +00:00
|
|
|
|
2018-12-11 09:51:20 +00:00
|
|
|
port := cfg.Port
|
2018-12-16 11:19:16 +00:00
|
|
|
if cfg.Dev {
|
|
|
|
// The node dev server will proxy index page requests and
|
|
|
|
// websocket connections to this port
|
|
|
|
port = "1337"
|
|
|
|
}
|
2015-05-01 20:59:46 +00:00
|
|
|
|
2018-12-16 11:19:16 +00:00
|
|
|
httpSrv := &http.Server{
|
|
|
|
Addr: net.JoinHostPort(cfg.Address, port),
|
|
|
|
}
|
2016-01-04 18:26:32 +00:00
|
|
|
|
2018-12-16 11:19:16 +00:00
|
|
|
if cfg.HTTPS.Enabled {
|
|
|
|
httpSrv.ReadTimeout = 5 * time.Second
|
|
|
|
httpSrv.WriteTimeout = 5 * time.Second
|
|
|
|
|
|
|
|
httpsSrv := &http.Server{
|
|
|
|
Addr: net.JoinHostPort(cfg.Address, cfg.HTTPS.Port),
|
|
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
|
|
WriteTimeout: 10 * time.Second,
|
|
|
|
IdleTimeout: 120 * time.Second,
|
|
|
|
Handler: d,
|
2016-01-04 18:26:32 +00:00
|
|
|
}
|
|
|
|
|
2018-12-17 11:45:33 +00:00
|
|
|
redirect := createHTTPSRedirect(cfg.HTTPS.Port, d)
|
2016-01-06 21:19:06 +00:00
|
|
|
|
2018-12-11 09:51:20 +00:00
|
|
|
if d.certExists() {
|
2018-12-16 11:19:16 +00:00
|
|
|
httpSrv.Handler = redirect
|
|
|
|
log.Println("[HTTP] Listening on port", port, "(HTTPS Redirect)")
|
|
|
|
go httpSrv.ListenAndServe()
|
|
|
|
|
|
|
|
log.Println("[HTTPS] Listening on port", cfg.HTTPS.Port)
|
|
|
|
log.Fatal(httpsSrv.ListenAndServeTLS(cfg.HTTPS.Cert, cfg.HTTPS.Key))
|
|
|
|
} else {
|
|
|
|
cache := certmagic.NewCache(certmagic.FileStorage{
|
|
|
|
Path: storage.Path.LetsEncrypt(),
|
|
|
|
})
|
|
|
|
|
|
|
|
magic := certmagic.NewWithCache(cache, certmagic.Config{
|
|
|
|
Agreed: true,
|
|
|
|
Email: cfg.LetsEncrypt.Email,
|
|
|
|
MustStaple: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
domains := []string{cfg.LetsEncrypt.Domain}
|
|
|
|
if cfg.LetsEncrypt.Domain == "" {
|
|
|
|
domains = []string{}
|
|
|
|
magic.OnDemand = &certmagic.OnDemandConfig{MaxObtain: 3}
|
2016-01-04 18:26:32 +00:00
|
|
|
}
|
|
|
|
|
2018-12-16 11:19:16 +00:00
|
|
|
err := magic.Manage(domains)
|
2016-01-04 18:26:32 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2016-01-06 21:19:06 +00:00
|
|
|
|
2018-12-16 11:19:16 +00:00
|
|
|
tlsConfig := magic.TLSConfig()
|
|
|
|
tlsConfig.MinVersion = tls.VersionTLS12
|
|
|
|
tlsConfig.CipherSuites = getCipherSuites()
|
|
|
|
tlsConfig.CurvePreferences = []tls.CurveID{
|
|
|
|
tls.X25519,
|
|
|
|
tls.CurveP256,
|
2017-04-15 02:48:24 +00:00
|
|
|
}
|
2018-12-16 11:19:16 +00:00
|
|
|
tlsConfig.PreferServerCipherSuites = true
|
|
|
|
httpsSrv.TLSConfig = tlsConfig
|
2016-01-06 21:19:06 +00:00
|
|
|
|
2018-12-16 11:19:16 +00:00
|
|
|
httpSrv.Handler = magic.HTTPChallengeHandler(redirect)
|
|
|
|
log.Println("[HTTP] Listening on port", port, "(HTTPS Redirect)")
|
|
|
|
go httpSrv.ListenAndServe()
|
|
|
|
|
|
|
|
log.Println("[HTTPS] Listening on port", cfg.HTTPS.Port)
|
|
|
|
log.Fatal(httpsSrv.ListenAndServeTLS("", ""))
|
2016-01-04 18:26:32 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-12-16 11:19:16 +00:00
|
|
|
httpSrv.ReadHeaderTimeout = 5 * time.Second
|
|
|
|
httpSrv.WriteTimeout = 10 * time.Second
|
|
|
|
httpSrv.IdleTimeout = 120 * time.Second
|
|
|
|
httpSrv.Handler = d
|
|
|
|
|
2016-01-04 18:26:32 +00:00
|
|
|
log.Println("[HTTP] Listening on port", port)
|
2018-12-16 11:19:16 +00:00
|
|
|
log.Fatal(httpSrv.ListenAndServe())
|
2016-01-04 18:26:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-09 06:32:47 +00:00
|
|
|
func (d *Dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2015-06-06 23:18:26 +00:00
|
|
|
if r.Method != "GET" {
|
2018-05-22 01:56:48 +00:00
|
|
|
fail(w, http.StatusNotFound)
|
2015-06-06 23:18:26 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-27 11:07:48 +00:00
|
|
|
if r.URL.Path == "/init" {
|
|
|
|
referer, err := url.Parse(r.Header.Get("Referer"))
|
|
|
|
if err != nil {
|
|
|
|
fail(w, http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
state := d.handleAuth(w, r, true, true)
|
2018-12-11 09:51:20 +00:00
|
|
|
data := d.getIndexData(r, referer.EscapedPath(), state)
|
2018-11-27 11:07:48 +00:00
|
|
|
|
|
|
|
writeJSON(w, r, data)
|
|
|
|
} else if strings.HasPrefix(r.URL.Path, "/ws") {
|
2018-05-22 01:56:48 +00:00
|
|
|
if !websocket.IsWebSocketUpgrade(r) {
|
|
|
|
fail(w, http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-11-17 10:52:36 +00:00
|
|
|
state := d.handleAuth(w, r, false, false)
|
2018-05-31 21:24:59 +00:00
|
|
|
if state == nil {
|
|
|
|
log.Println("[Auth] No state")
|
2018-05-22 01:56:48 +00:00
|
|
|
fail(w, http.StatusInternalServerError)
|
2016-01-15 01:27:30 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
d.upgradeWS(w, r, state)
|
2015-06-06 23:18:26 +00:00
|
|
|
} else {
|
2018-05-31 21:24:59 +00:00
|
|
|
d.serveFiles(w, r)
|
2015-06-06 23:18:26 +00:00
|
|
|
}
|
2015-05-01 20:59:46 +00:00
|
|
|
}
|
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
func (d *Dispatch) upgradeWS(w http.ResponseWriter, r *http.Request, state *State) {
|
|
|
|
conn, err := d.upgrader.Upgrade(w, r, w.Header())
|
2015-05-01 22:20:22 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-05-31 21:24:59 +00:00
|
|
|
newWSHandler(conn, state, r).run()
|
2015-05-01 22:20:22 +00:00
|
|
|
}
|
|
|
|
|
2018-12-17 11:45:33 +00:00
|
|
|
func createHTTPSRedirect(portHTTPS string, fallback http.Handler) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2016-01-04 18:26:32 +00:00
|
|
|
host, _, err := net.SplitHostPort(r.Host)
|
|
|
|
if err != nil {
|
|
|
|
host = r.Host
|
2015-05-01 20:59:46 +00:00
|
|
|
}
|
2016-01-04 18:26:32 +00:00
|
|
|
|
2018-12-17 11:45:33 +00:00
|
|
|
if netutil.IsPrivate(host) {
|
|
|
|
fallback.ServeHTTP(w, r)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-04 18:26:32 +00:00
|
|
|
u := url.URL{
|
|
|
|
Scheme: "https",
|
|
|
|
Host: net.JoinHostPort(host, portHTTPS),
|
|
|
|
Path: r.RequestURI,
|
|
|
|
}
|
|
|
|
|
2018-12-16 11:19:16 +00:00
|
|
|
w.Header().Set("Connection", "close")
|
2016-01-04 18:26:32 +00:00
|
|
|
w.Header().Set("Location", u.String())
|
|
|
|
w.WriteHeader(http.StatusMovedPermanently)
|
2018-12-17 11:45:33 +00:00
|
|
|
}
|
2016-01-04 18:26:32 +00:00
|
|
|
}
|
|
|
|
|
2018-05-22 01:56:48 +00:00
|
|
|
func fail(w http.ResponseWriter, code int) {
|
|
|
|
http.Error(w, http.StatusText(code), code)
|
|
|
|
}
|