diff --git a/irc/client_lookup_set.go b/irc/client_lookup_set.go index 26907b7b..89260615 100644 --- a/irc/client_lookup_set.go +++ b/irc/client_lookup_set.go @@ -43,6 +43,10 @@ func NewClientLookupSet() *ClientLookupSet { } } +func (clients *ClientLookupSet) Count() int { + return len(clients.ByNick) +} + func (clients *ClientLookupSet) Has(nick string) bool { casefoldedName, err := CasefoldName(nick) if err == nil { diff --git a/irc/config.go b/irc/config.go index 0df1cf87..c8b08e77 100644 --- a/irc/config.go +++ b/irc/config.go @@ -89,6 +89,11 @@ func (conf *OperConfig) PasswordBytes() []byte { return bytes } +type RestAPIConfig struct { + Enabled bool + Listen string +} + type ConnectionLimitsConfig struct { CidrLenIPv4 int `yaml:"cidr-len-ipv4"` CidrLenIPv6 int `yaml:"cidr-len-ipv6"` @@ -108,6 +113,7 @@ type Config struct { Listen []string Wslisten string `yaml:"ws-listen"` TLSListeners map[string]*TLSListenConfig `yaml:"tls-listeners"` + RestAPI RestAPIConfig `yaml:"rest-api"` CheckIdent bool `yaml:"check-ident"` Log string MOTD string diff --git a/irc/dline.go b/irc/dline.go index b8e9bc19..b4b3c4ff 100644 --- a/irc/dline.go +++ b/irc/dline.go @@ -80,6 +80,20 @@ func NewDLineManager() *DLineManager { return &dm } +// AllBans returns all bans (for use with APIs, etc). +func (dm *DLineManager) AllBans() map[string]IPBanInfo { + allb := make(map[string]IPBanInfo) + + for name, info := range dm.addresses { + allb[name] = info.Info + } + for name, info := range dm.networks { + allb[name] = info.Info + } + + return allb +} + // AddNetwork adds a network to the blocked list. func (dm *DLineManager) AddNetwork(network net.IPNet, length *IPRestrictTime, reason string, operReason string) { netString := network.String() diff --git a/irc/rest_api.go b/irc/rest_api.go new file mode 100644 index 00000000..28a2e095 --- /dev/null +++ b/irc/rest_api.go @@ -0,0 +1,70 @@ +// Copyright (c) 2016- Daniel Oaks +// released under the MIT license + +// viewing and modifying accounts, registered channels, dlines, rehashing, etc + +package irc + +import ( + "encoding/json" + "net/http" + + "fmt" + + "github.com/gorilla/mux" +) + +const restErr = "{\"error\":\"An unknown error occurred\"}" + +// restAPIServer is used to keep a link to the current running server since this is the best +// way to do it, given how HTTP handlers dispatch and work. +var restAPIServer *Server + +type restStatusResp struct { + Clients int `json:"clients"` + Opers int `json:"opers"` + Channels int `json:"channels"` +} + +type restDLinesResp struct { + DLines map[string]IPBanInfo `json:"dlines"` +} + +func restStatus(w http.ResponseWriter, r *http.Request) { + rs := restStatusResp{ + Clients: restAPIServer.clients.Count(), + Opers: len(restAPIServer.operators), + Channels: len(restAPIServer.channels), + } + b, err := json.Marshal(rs) + if err != nil { + fmt.Fprintln(w, restErr) + } else { + fmt.Fprintln(w, string(b)) + } +} + +func restDLines(w http.ResponseWriter, r *http.Request) { + rs := restDLinesResp{ + DLines: restAPIServer.dlines.AllBans(), + } + b, err := json.Marshal(rs) + if err != nil { + fmt.Fprintln(w, restErr) + } else { + fmt.Fprintln(w, string(b)) + } +} + +func (s *Server) startRestAPI() { + // so handlers can ref it later + restAPIServer = s + + // start router + r := mux.NewRouter() + r.HandleFunc("/status", restStatus) + r.HandleFunc("/dlines", restDLines) + + // start api + go http.ListenAndServe(s.restAPI.Listen, r) +} diff --git a/irc/server.go b/irc/server.go index c0f544c8..c0797f11 100644 --- a/irc/server.go +++ b/irc/server.go @@ -100,6 +100,7 @@ type Server struct { passwords *PasswordManager rehashMutex sync.Mutex rehashSignal chan os.Signal + restAPI *RestAPIConfig signals chan os.Signal store buntdb.DB whoWas *WhoWasList @@ -183,6 +184,7 @@ func NewServer(configFilename string, config *Config) *Server { operators: opers, signals: make(chan os.Signal, len(ServerExitSignals)), rehashSignal: make(chan os.Signal, 1), + restAPI: &config.Server.RestAPI, whoWas: NewWhoWasList(config.Limits.WhowasEntries), checkIdent: config.Server.CheckIdent, } @@ -260,6 +262,12 @@ func NewServer(configFilename string, config *Config) *Server { server.setISupport() + // start API if enabled + if server.restAPI.Enabled { + Log.info.Printf("%s rest API started on %s .", server.name, server.restAPI.Listen) + server.startRestAPI() + } + return server } diff --git a/oragono.yaml b/oragono.yaml index 107fc6e6..8c511bf5 100644 --- a/oragono.yaml +++ b/oragono.yaml @@ -27,6 +27,14 @@ server: key: tls.key cert: tls.crt + # rest API, for use with web interface + rest-api: + # whether the API is enabled or not + enabled: true + + # rest API listening port + listen: "localhost:8090" + # use ident protocol to get usernames check-ident: true