add readme, add more comments to main.go

This commit is contained in:
Sundog Jones 2021-04-17 09:32:37 -07:00
parent db28cfbcdb
commit b2cfe0f1ab
2 changed files with 44 additions and 18 deletions

6
README.md Normal file
View File

@ -0,0 +1,6 @@
# go-bbs
## a basic multiuser bulletin board system written in Go
This is a simple framework for developing and deploying a console-based bulletin board system.
It is currently under active development but not ready for use quite yet.

56
main.go
View File

@ -1,3 +1,5 @@
// go-bbs provides a basic framework for a multiuser bulletin board server application
// it is indended to be customized by the operator of the bulletin board
package main
import (
@ -26,6 +28,11 @@ import (
type InChanKey string
type OutChanKey string
// ioHandler is the gaio.Watcher's main loop
// it wait for I/O calls to the watcher's Read and Write methods
// and tracks the state of the call, dispatching the appropriate gaio.OpResult
// to the proper channel based on whether it was a read (input from the remote client)
// or a write (output to the remote client)
func ioHandler(w *gaio.Watcher, WatcherControl *chan string) {
for {
select {
@ -71,6 +78,9 @@ func ioHandler(w *gaio.Watcher, WatcherControl *chan string) {
}
}
// main is the main loop of the server
// it sets up both the network listener as well as the gaio.Watcher that will handle
// I/O for each incoming network connection
func main() {
port := flag.Int("port", 3333, "Port to accept connections on.")
host := flag.String("host", "127.0.0.1", "Host or IP to bind to")
@ -131,6 +141,9 @@ func main() {
}
}
// menuHandler is the main loop for each individual remote connection
// it receives input over the connection's InChan channel (part of its context)
// as a gaio.OpResult
func menuHandler(ctx context.Context, conn net.Conn, w *gaio.Watcher) {
var wg sync.WaitGroup
@ -319,10 +332,16 @@ func menuHandler(ctx context.Context, conn net.Conn, w *gaio.Watcher) {
log.Println("terminating menucontrol for client")
}
// screenHandler handles non-interactive commands
// it takes a screen name and an optional struct of data to be parsed into the screen's template
// it returns the populated screen as a byte array and an error if there's a problem loading the template
// screen templates are simple text files with optional go template directives. if the text file includes
// ANSI control sequences they will be passed on to the remote client as well.
// TODO: add caching of static screens, move ANSI handling out to optional template directives as well
func screenHandler(screenName string, tplData interface{}) (screen []byte, err error) {
var screenBuf *bytes.Buffer
if tmpl, err := template.ParseFiles("screens/" + screenName + ".ans"); err != nil {
log.Printf("err loading welcome template: %v\n", err)
log.Printf("err loading welcome template: %v\n", err) // // TODO: better error handling
} else {
screenBuf = new(bytes.Buffer)
tmpl.Execute(screenBuf, tplData)
@ -331,6 +350,11 @@ func screenHandler(screenName string, tplData interface{}) (screen []byte, err e
return screenBuf.Bytes(), err
}
// doorHandler handles interactive commands
// it halts the processing of the menuHandler and allows the doorHandler full duplex I/O
// between the remote connection and a local program's stdin/stdout
// doorHandler returns control to menuHandler once the program completes and exits
// TODO: add argument for program, program's prompt, launch text, and exit text
func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.WaitGroup) error {
defer menuwg.Done()
@ -340,21 +364,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
var ok bool
if inChan, _, ok = openconn.FromContext(ctx); !ok {
log.Println("Could not get inChan/outChan from context!", ok, ctx)
log.Println("Could not get inChan/outChan from context!", ok, ctx) // TODO: better error handling
}
cmd := exec.Command("/usr/games/adventure")
cmd := exec.Command("/usr/games/adventure") // TODO: move command path and args to an argument to doorHandler
ptmx, err := pty.Start(cmd)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
defer func() { _ = ptmx.Close() }() // Best effort. TODO: better error handling, better zombie handling
// Can I do this?
if term.IsTerminal(int(ptmx.Fd())) {
log.Println("Making cmd terminal raw")
oldState, _ := term.MakeRaw(int(ptmx.Fd()))
defer term.Restore(int(ptmx.Fd()), oldState)
}
@ -370,13 +392,12 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
select {
case result, ok := <-inChan:
var l int
log.Println("i")
if ok {
if result.Operation == gaio.OpRead && result.Size > 0 {
l, err = ptmx.Write(result.Buffer[:result.Size])
if err != nil {
if err == io.EOF {
log.Println("EOF on cmd.write!!!")
log.Println("EOF on cmd.write!!!") // TODO: better error handling
break IOLoop
}
}
@ -387,20 +408,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
}
} else {
log.Println("yikes, problem getting a result from inChan in doorHandler!")
log.Println("yikes, problem getting a result from inChan in doorHandler!") // TODO: better error handling
}
case <-Terminator:
log.Println("Terminator'd!!!")
break IOLoop
default:
if !waitingForInput {
log.Println("o")
ptmx.SetReadDeadline(time.Now().Add(1 * time.Second))
var l int
outBuf := make([]byte, 4096)
if l, err = ptmx.Read(outBuf); err != nil {
if err == io.EOF {
log.Println("EOF on cmd stdout - outta here")
// EOF isn't actually an error in this case, it just means we're done
break IOLoop
} else if errors.Is(err, os.ErrDeadlineExceeded) {
// output deadline exceeded - get some input instead
@ -410,20 +430,19 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
// program exited - get outta here
break IOLoop
} else {
log.Printf("err reading from cmd term: %v\n", err)
log.Printf("err reading from cmd term: %v\n", err) // TODO: better error handling
break IOLoop
}
}
if l > 0 {
w.Write(ctx, c, outBuf[:l])
log.Printf("wrote %d bytes to watcher: %v\n", l, string(outBuf[:l]))
if strings.Contains(string(outBuf[:l]), ">") {
if strings.Contains(string(outBuf[:l]), ">") { // TODO: abstract out prompt string as argument to doorHandler
// a prompt appeared!
waitingForInput = true
}
} else {
// zero output? let's get some input?
// waitingForInput = true
// zero output? let's get some input opportunistically
w.Read(ctx, c, nil)
}
}
@ -435,7 +454,7 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
log.Println("waiting for cmd")
exitErr := cmd.Wait()
if exitErr != nil {
log.Printf("cmd exited with err: %v\n", exitErr)
log.Printf("cmd exited with err: %v\n", exitErr) // TODO: better error handling
}
log.Println("leaving door handler")
return nil
@ -446,11 +465,12 @@ func getUptime(wg *sync.WaitGroup) string {
defer wg.Done()
out, err := exec.Command("uptime").Output()
if err != nil {
return string(err.Error()) // might as well pass on the love to my future co-workers for debugging assistance
return string(err.Error()) // TODO: better error handling
}
return string(out)
}
// returns output of a bash script that collects current wireguard peering connections and their traffic totals
func getTubePeers(wg *sync.WaitGroup) string {
defer wg.Done()
out, err := exec.Command("/usr/local/bin/getTubesPeers.bash").Output()
@ -477,7 +497,7 @@ func getTubePeers(wg *sync.WaitGroup) string {
if err != nil {
log.Printf("err loading getTubePeers: %v\n", err)
return string(err.Error()) // again, might as well pass on the love to my future co-workers for debugging assistance
return string(err.Error()) // TODO: better error handling
}
return outStr
}