Archived
1
0

refactor mod stuff

This commit is contained in:
Jeff Becker 2017-04-04 07:48:45 -04:00
parent e8fa40c0ca
commit e87d739392
9 changed files with 271 additions and 140 deletions

View File

@ -8,7 +8,7 @@ import (
type CacheInterface interface { type CacheInterface interface {
RegenAll() RegenAll()
RegenFrontPage() RegenFrontPage()
RegenOnModEvent(string, string, string, int) RegenOnModEvent(newsgroup, msgid, root string, page int)
RegenerateBoard(group string) RegenerateBoard(group string)
Regen(msg ArticleEntry) Regen(msg ArticleEntry)

View File

@ -535,28 +535,6 @@ func (self *NNTPDaemon) Run() {
self.allow_anon_attachments = self.conf.daemon["allow_anon_attachments"] == "1" self.allow_anon_attachments = self.conf.daemon["allow_anon_attachments"] == "1"
self.allow_attachments = self.conf.daemon["allow_attachments"] == "1" self.allow_attachments = self.conf.daemon["allow_attachments"] == "1"
// do we enable the frontend?
if self.conf.frontend["enable"] == "1" {
log.Printf("frontend %s enabled", self.conf.frontend["name"])
cache_host := self.conf.cache["host"]
cache_port := self.conf.cache["port"]
cache_user := self.conf.cache["user"]
cache_passwd := self.conf.cache["password"]
self.cache = NewCache(self.conf.cache["type"], cache_host, cache_port, cache_user, cache_passwd, self.conf.cache, self.conf.frontend, self.database, self.store)
script, ok := self.conf.frontend["markup_script"]
if ok {
err = SetMarkupScriptFile(script)
if err != nil {
log.Println("failed to load markup script", err)
}
}
self.frontend = NewHTTPFrontend(self, self.cache, self.conf.frontend, self.conf.worker["url"])
go self.frontend.Mainloop()
}
// set up admin user if it's specified in the config // set up admin user if it's specified in the config
pubkey, ok := self.conf.frontend["admin_key"] pubkey, ok := self.conf.frontend["admin_key"]
if ok { if ok {
@ -572,6 +550,9 @@ func (self *NNTPDaemon) Run() {
} }
} }
// start frontend
go self.frontend.Mainloop()
log.Println("we have", len(self.conf.feeds), "feeds") log.Println("we have", len(self.conf.feeds), "feeds")
defer self.listener.Close() defer self.listener.Close()
@ -647,7 +628,6 @@ func (self *NNTPDaemon) Run() {
log.Println("started worker", threads) log.Println("started worker", threads)
threads-- threads--
} }
// start accepting incoming connections // start accepting incoming connections
self.acceptloop() self.acceptloop()
<-self.done <-self.done
@ -838,7 +818,6 @@ func (self *NNTPDaemon) pump_article_requests() {
} }
func (self *NNTPDaemon) poll(worker int) { func (self *NNTPDaemon) poll(worker int) {
modchnl := self.mod.MessageChan()
for { for {
select { select {
case msgid := <-self.infeed_load: case msgid := <-self.infeed_load:
@ -863,7 +842,7 @@ func (self *NNTPDaemon) poll(worker int) {
} }
// send to mod panel // send to mod panel
if group == "ctl" { if group == "ctl" {
modchnl <- msgid self.mod.HandleMessage(msgid)
} }
// inform callback hooks // inform callback hooks
self.informHooks(group, msgid, ref) self.informHooks(group, msgid, ref)
@ -1081,11 +1060,36 @@ func (self *NNTPDaemon) Setup() {
log.Println("set up article store...") log.Println("set up article store...")
self.store = createArticleStore(self.conf.store, self.database) self.store = createArticleStore(self.conf.store, self.database)
self.mod = modEngine{ // do we enable the frontend?
if self.conf.frontend["enable"] == "1" {
log.Printf("frontend %s enabled", self.conf.frontend["name"])
cache_host := self.conf.cache["host"]
cache_port := self.conf.cache["port"]
cache_user := self.conf.cache["user"]
cache_passwd := self.conf.cache["password"]
self.cache = NewCache(self.conf.cache["type"], cache_host, cache_port, cache_user, cache_passwd, self.conf.cache, self.conf.frontend, self.database, self.store)
script, ok := self.conf.frontend["markup_script"]
if ok {
err = SetMarkupScriptFile(script)
if err != nil {
log.Println("failed to load markup script", err)
}
}
self.frontend = NewHTTPFrontend(self, self.cache, self.conf.frontend, self.conf.worker["url"])
}
self.mod = &modEngine{
store: self.store, store: self.store,
database: self.database, database: self.database,
chnl: make(chan string), regen: self.frontend.RegenOnModEvent,
} }
// inject DB into template engine // inject DB into template engine
template.DB = self.database template.DB = self.database
} }
func (daemon *NNTPDaemon) ModEngine() ModEngine {
return daemon.mod
}

View File

@ -63,7 +63,6 @@ func (self expire) ExpirePost(messageID string) {
} }
func (self expire) ExpireGroup(newsgroup string, keep int) { func (self expire) ExpireGroup(newsgroup string, keep int) {
log.Println("Expire group", newsgroup, keep)
threads := self.database.GetRootPostsForExpiration(newsgroup, keep) threads := self.database.GetRootPostsForExpiration(newsgroup, keep)
for _, root := range threads { for _, root := range threads {
self.ExpireThread(newsgroup, root) self.ExpireThread(newsgroup, root)
@ -127,7 +126,6 @@ func (self expire) ExpireOrphans() {
} }
func (self expire) handleEvent(ev deleteEvent) { func (self expire) handleEvent(ev deleteEvent) {
log.Println("expire", ev.MessageID())
atts := self.database.GetPostAttachments(ev.MessageID()) atts := self.database.GetPostAttachments(ev.MessageID())
// remove all attachments // remove all attachments
if atts != nil { if atts != nil {
@ -147,5 +145,5 @@ func (self expire) handleEvent(ev deleteEvent) {
log.Println("failed to delete article", err) log.Println("failed to delete article", err)
} }
// remove article // remove article
os.Remove(ev.Path()) self.store.Remove(ev.MessageID())
} }

View File

@ -36,4 +36,6 @@ type Frontend interface {
// trigger a manual regen of indexes for a root post // trigger a manual regen of indexes for a root post
Regen(msg ArticleEntry) Regen(msg ArticleEntry)
// regenerate on mod event
RegenOnModEvent(newsgroup, msgid, root string, page int)
} }

View File

@ -1483,7 +1483,7 @@ func (self *httpFrontend) Mainloop() {
var err error var err error
// run daemon's mod engine with our frontend // run daemon's mod engine with our frontend
go RunModEngine(self.daemon.mod, self.cache.RegenOnModEvent) // go RunModEngine(self.daemon.mod, self.cache.RegenOnModEvent)
// start cache // start cache
self.cache.Start() self.cache.Start()
@ -1517,6 +1517,10 @@ func (self *httpFrontend) endLiveUI() {
} }
} }
func (self *httpFrontend) RegenOnModEvent(newsgroup, msgid, root string, page int) {
self.cache.RegenOnModEvent(newsgroup, msgid, root, page)
}
// create a new http based frontend // create a new http based frontend
func NewHTTPFrontend(daemon *NNTPDaemon, cache CacheInterface, config map[string]string, url string) Frontend { func NewHTTPFrontend(daemon *NNTPDaemon, cache CacheInterface, config map[string]string, url string) Frontend {
template.Minimize = config["minimize_html"] == "1" template.Minimize = config["minimize_html"] == "1"

View File

@ -46,6 +46,12 @@ func (self multiFrontend) PostsChan() chan frontendPost {
return self.muxedpostchan return self.muxedpostchan
} }
func (self multiFrontend) RegenOnModEvent(newsgroup, msgid, root string, page int) {
for idx := range self.frontends {
self.frontends[idx].RegenOnModEvent(newsgroup, msgid, root, page)
}
}
func MuxFrontends(fronts ...Frontend) Frontend { func MuxFrontends(fronts ...Frontend) Frontend {
var front multiFrontend var front multiFrontend
front.muxedpostchan = make(chan frontendPost, 64) front.muxedpostchan = make(chan frontendPost, 64)

View File

@ -23,14 +23,9 @@ type AdminFunc func(param map[string]interface{}) (interface{}, error)
// interface for moderation ui // interface for moderation ui
type ModUI interface { type ModUI interface {
// channel for daemon to poll for nntp articles from the mod ui
MessageChan() chan NNTPMessage
// check if this key is allowed to access // check if this key is allowed to access
// return true if it can otherwise false // return true if it can otherwise false
CheckKey(privkey, scope string) (bool, error) CheckKey(privkey, scope string) (bool, error)
// serve the base page // serve the base page
ServeModPage(wr http.ResponseWriter, r *http.Request) ServeModPage(wr http.ResponseWriter, r *http.Request)
// handle a login POST request // handle a login POST request
@ -49,6 +44,8 @@ type ModUI interface {
HandleKeyGen(wr http.ResponseWriter, r *http.Request) HandleKeyGen(wr http.ResponseWriter, r *http.Request)
// handle admin command // handle admin command
HandleAdminCommand(wr http.ResponseWriter, r *http.Request) HandleAdminCommand(wr http.ResponseWriter, r *http.Request)
// get outbound message channel
MessageChan() chan NNTPMessage
} }
type ModEvent interface { type ModEvent interface {
@ -149,10 +146,10 @@ func wrapModMessage(mm ModMessage) NNTPMessage {
} }
type ModEngine interface { type ModEngine interface {
// chan to send the mod engine posts given message_id // load and handle a mod message from ctl after it's verified
MessageChan() chan string HandleMessage(msgid string)
// delete post of a poster // delete post of a poster
DeletePost(msgid string, regen RegenFunc) error DeletePost(msgid string) error
// ban a cidr // ban a cidr
BanAddress(cidr string) error BanAddress(cidr string) error
// do we allow this public key to delete this message-id ? // do we allow this public key to delete this message-id ?
@ -161,28 +158,28 @@ type ModEngine interface {
AllowBan(pubkey string) bool AllowBan(pubkey string) bool
// load a mod message // load a mod message
LoadMessage(msgid string) NNTPMessage LoadMessage(msgid string) NNTPMessage
// execute 1 mod action line by a mod with pubkey
Execute(ev ModEvent, pubkey string)
// do a mod event unconditionally
Do(ev ModEvent)
} }
type modEngine struct { type modEngine struct {
database Database database Database
store ArticleStore store ArticleStore
chnl chan string regen RegenFunc
} }
func (self modEngine) LoadMessage(msgid string) NNTPMessage { func (self *modEngine) LoadMessage(msgid string) NNTPMessage {
return self.store.GetMessage(msgid) return self.store.GetMessage(msgid)
} }
func (self modEngine) MessageChan() chan string { func (self *modEngine) BanAddress(cidr string) (err error) {
return self.chnl
}
func (self modEngine) BanAddress(cidr string) (err error) {
return self.database.BanAddr(cidr) return self.database.BanAddr(cidr)
} }
func (self modEngine) DeletePost(msgid string, regen RegenFunc) (err error) { func (self *modEngine) DeletePost(msgid string) (err error) {
hdr, err := self.database.GetHeadersForMessage(msgid) hdr := self.store.GetHeaders(msgid)
var delposts []string var delposts []string
var page int64 var page int64
var ref, group string var ref, group string
@ -231,10 +228,6 @@ func (self modEngine) DeletePost(msgid string, regen RegenFunc) (err error) {
os.Remove(f) os.Remove(f)
} }
if rootmsgid != "" {
self.database.DeleteThread(rootmsgid)
}
for _, delmsg := range delposts { for _, delmsg := range delposts {
// delete article from post database // delete article from post database
err = self.database.DeleteArticle(delmsg) err = self.database.DeleteArticle(delmsg)
@ -243,12 +236,17 @@ func (self modEngine) DeletePost(msgid string, regen RegenFunc) (err error) {
} }
// ban article // ban article
self.database.BanArticle(delmsg, "deleted by moderator") self.database.BanArticle(delmsg, "deleted by moderator")
self.store.Remove(delmsg)
} }
regen(group, msgid, ref, int(page))
if rootmsgid != "" {
self.database.DeleteThread(rootmsgid)
}
self.regen(group, msgid, ref, int(page))
return nil return nil
} }
func (self modEngine) AllowBan(pubkey string) bool { func (self *modEngine) AllowBan(pubkey string) bool {
is_admin, _ := self.database.CheckAdminPubkey(pubkey) is_admin, _ := self.database.CheckAdminPubkey(pubkey)
if is_admin { if is_admin {
// admins can do whatever // admins can do whatever
@ -257,7 +255,7 @@ func (self modEngine) AllowBan(pubkey string) bool {
return self.database.CheckModPubkeyGlobal(pubkey) return self.database.CheckModPubkeyGlobal(pubkey)
} }
func (self modEngine) AllowDelete(pubkey, msgid string) (allow bool) { func (self *modEngine) AllowDelete(pubkey, msgid string) (allow bool) {
is_admin, _ := self.database.CheckAdminPubkey(pubkey) is_admin, _ := self.database.CheckAdminPubkey(pubkey)
if is_admin { if is_admin {
// admins can do whatever // admins can do whatever
@ -277,88 +275,166 @@ func (self modEngine) AllowDelete(pubkey, msgid string) (allow bool) {
return return
} }
// run a mod engine logic mainloop func (mod *modEngine) HandleMessage(msgid string) {
func RunModEngine(mod ModEngine, regen RegenFunc) { nntp := mod.store.GetMessage(msgid)
if nntp == nil {
chnl := mod.MessageChan() log.Println("failed to load", msgid, "in mod engine, missing message")
for { return
msgid := <-chnl }
nntp := mod.LoadMessage(msgid) // sanity check
if nntp == nil { if nntp.Newsgroup() == "ctl" {
log.Println("failed to load mod message", msgid) pubkey := nntp.Pubkey()
continue for _, line := range strings.Split(nntp.Message(), "\n") {
} line = strings.Trim(line, "\r\t\n ")
// sanity check ev := ParseModEvent(line)
if nntp.Newsgroup() == "ctl" { mod.Execute(ev, pubkey)
pubkey := nntp.Pubkey()
for _, line := range strings.Split(nntp.Message(), "\n") {
line = strings.Trim(line, "\r\t\n ")
ev := ParseModEvent(line)
action := ev.Action()
if action == "delete" {
msgid := ev.Target()
if !ValidMessageID(msgid) {
// invalid message-id
log.Println("invalid message-id for mod delete", msgid, "from", pubkey)
continue
}
// this is a delete action
if mod.AllowDelete(pubkey, msgid) {
err := mod.DeletePost(msgid, regen)
if err != nil {
log.Println(msgid, err)
}
} else {
log.Printf("pubkey=%s will not delete %s not trusted", pubkey, msgid)
}
} else if action == "overchan-inet-ban" {
// ban action
target := ev.Target()
if target[0] == '[' {
// probably a literal ipv6 rangeban
if mod.AllowBan(pubkey) {
err := mod.BanAddress(target)
if err != nil {
log.Println("failed to do literal ipv6 range ban on", target, err)
}
} else {
log.Println("ignoring literal ipv6 rangeban from", pubkey, "as they are not allowed to ban")
}
continue
}
parts := strings.Split(target, ":")
if len(parts) == 3 {
// encrypted ip
encaddr, key := parts[0], parts[1]
cidr := decAddr(encaddr, key)
if cidr == "" {
log.Println("failed to decrypt inet ban")
} else if mod.AllowBan(pubkey) {
err := mod.BanAddress(cidr)
if err != nil {
log.Println("failed to do range ban on", cidr, err)
}
} else {
log.Println("ingoring encrypted-ip inet ban from", pubkey, "as they are not allowed to ban")
}
} else if len(parts) == 1 {
// literal cidr
cidr := parts[0]
if mod.AllowBan(pubkey) {
err := mod.BanAddress(cidr)
if err != nil {
log.Println("failed to do literal range ban on", cidr, err)
}
} else {
log.Println("ingoring literal cidr range ban from", pubkey, "as they are not allowed to ban")
}
} else {
log.Printf("invalid overchan-inet-ban: target=%s", target)
}
} else {
log.Println("invalid mod action", action, "from", pubkey)
}
}
} }
} }
} }
func (mod *modEngine) Do(ev ModEvent) {
action := ev.Action()
if action == "delete" {
msgid := ev.Target()
if !ValidMessageID(msgid) {
// invalid message-id
log.Println("invalid message-id", msgid)
return
}
err := mod.DeletePost(msgid)
if err != nil {
log.Println(msgid, err)
} else {
log.Println("deleted", msgid)
}
} else if action == "overchan-inet-ban" {
// ban action
target := ev.Target()
if target[0] == '[' {
err := mod.BanAddress(target)
if err != nil {
log.Println("failed to do literal ipv6 range ban on", target, err)
} else {
log.Println("banned", target)
}
return
}
parts := strings.Split(target, ":")
if len(parts) == 3 {
// encrypted ip
encaddr, key := parts[0], parts[1]
cidr := decAddr(encaddr, key)
if cidr == "" {
log.Println("failed to decrypt inet ban")
} else {
err := mod.BanAddress(cidr)
if err != nil {
log.Println("failed to do range ban on", cidr, err)
} else {
log.Println("banned", cidr)
}
}
} else if len(parts) == 2 {
// x-encrypted-ip ban without pad
err := mod.database.BanEncAddr(parts[0])
if err != nil {
log.Println("failed to ban encrypted ip", err)
} else {
log.Println("banned poster", parts[0])
}
} else if len(parts) == 1 {
// literal cidr
cidr := parts[0]
err := mod.BanAddress(cidr)
if err != nil {
log.Println("failed to do literal range ban on", cidr, err)
} else {
log.Println("banned cidr", cidr)
}
} else {
log.Printf("invalid overchan-inet-ban: target=%s", target)
}
} else {
log.Println("invalid mod action", action)
}
}
func (mod *modEngine) Execute(ev ModEvent, pubkey string) {
action := ev.Action()
if action == "delete" {
msgid := ev.Target()
if !ValidMessageID(msgid) {
// invalid message-id
log.Println("invalid message-id for mod delete from", pubkey)
return
}
// this is a delete action
if mod.AllowDelete(pubkey, msgid) {
err := mod.DeletePost(msgid)
if err != nil {
log.Println(msgid, err)
}
} else {
log.Printf("pubkey=%s will not delete %s not trusted", pubkey, msgid)
}
} else if action == "overchan-inet-ban" {
// ban action
target := ev.Target()
if target[0] == '[' {
// probably a literal ipv6 rangeban
if mod.AllowBan(pubkey) {
err := mod.BanAddress(target)
if err != nil {
log.Println("failed to do literal ipv6 range ban on", target, err)
}
} else {
log.Println("ignoring literal ipv6 rangeban from", pubkey, "as they are not allowed to ban")
}
return
}
parts := strings.Split(target, ":")
if len(parts) == 3 {
// encrypted ip
encaddr, key := parts[0], parts[1]
cidr := decAddr(encaddr, key)
if cidr == "" {
log.Println("failed to decrypt inet ban")
} else if mod.AllowBan(pubkey) {
err := mod.BanAddress(cidr)
if err != nil {
log.Println("failed to do range ban on", cidr, err)
}
} else {
log.Println("ingoring encrypted-ip inet ban from", pubkey, "as they are not allowed to ban")
}
} else if len(parts) == 2 {
// x-encrypted-ip ban without pad
if mod.AllowBan(pubkey) {
err := mod.database.BanEncAddr(parts[0])
if err != nil {
log.Println("failed to ban encrypted ip", err)
}
} else {
log.Println("ignoring encrypted-ip ban from", pubkey, "as they are not allowed to ban")
}
} else if len(parts) == 1 {
// literal cidr
cidr := parts[0]
if mod.AllowBan(pubkey) {
err := mod.BanAddress(cidr)
if err != nil {
log.Println("failed to do literal range ban on", cidr, err)
}
} else {
log.Println("ingoring literal cidr range ban from", pubkey, "as they are not allowed to ban")
}
} else {
log.Printf("invalid overchan-inet-ban: target=%s", target)
}
} else {
log.Println("invalid mod action", action, "from", pubkey)
}
}

View File

@ -70,6 +70,9 @@ type ArticleStore interface {
// get thumbnail info of file by path // get thumbnail info of file by path
ThumbInfo(fpath string) (ThumbInfo, error) ThumbInfo(fpath string) (ThumbInfo, error)
// delete message by message-id
Remove(msgid string) error
} }
type articleStore struct { type articleStore struct {
directory string directory string
@ -140,6 +143,16 @@ func (self *articleStore) Init() {
} }
} }
func (self *articleStore) Remove(msgid string) (err error) {
if ValidMessageID(msgid) {
fpath := self.GetFilename(msgid)
err = os.Remove(fpath)
} else {
err = errors.New("invalid message-id: " + msgid)
}
return
}
func (self *articleStore) RegisterSigned(msgid, pk string) (err error) { func (self *articleStore) RegisterSigned(msgid, pk string) (err error) {
err = self.database.RegisterSigned(msgid, pk) err = self.database.RegisterSigned(msgid, pk)
return return

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"log" "log"
"os" "os"
@ -44,7 +45,32 @@ func main() {
if len(os.Args) > 2 { if len(os.Args) > 2 {
tool := os.Args[2] tool := os.Args[2]
if tool == "mod" { if tool == "mod" {
if len(os.Args) >= 5 { if len(os.Args) == 4 && os.Args[3] == "do" {
daemon.Setup()
eng := daemon.ModEngine()
r := bufio.NewReader(os.Stdin)
var err error
for err == nil {
var line string
fmt.Print("\nmod> ")
line, err = r.ReadString(10)
if err == nil {
line = strings.Trim(line, "\n")
if line == "help" {
fmt.Println("usage:")
fmt.Println("delete <message-id>")
fmt.Println("overchan-inet-ban 1.1.1.1/32")
fmt.Println("exit")
} else if line == "quit" || line == "exit" {
fmt.Println("bai")
return
} else {
ev := srnd.ParseModEvent(line)
eng.Do(ev)
}
}
}
} else if len(os.Args) >= 5 {
action := os.Args[3] action := os.Args[3]
if action == "add" { if action == "add" {
pk := os.Args[4] pk := os.Args[4]
@ -62,9 +88,11 @@ func main() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else {
fmt.Fprintf(os.Stdout, "usage: %s tool mod [[add|del] pubkey]|[do modactiongoeshere]\n", os.Args[0])
} }
} else { } else {
fmt.Fprintf(os.Stdout, "usage: %s tool mod [add|del] pubkey\n", os.Args[0]) fmt.Fprintf(os.Stdout, "usage: %s tool mod [[add|del] pubkey]|[do modactiongoeshere]\n", os.Args[0])
} }
} else if tool == "rethumb" { } else if tool == "rethumb" {
if len(os.Args) >= 4 { if len(os.Args) >= 4 {