diff --git a/cmd/tokiko/main.go b/cmd/tokiko/main.go new file mode 100644 index 0000000..71aa73c --- /dev/null +++ b/cmd/tokiko/main.go @@ -0,0 +1,179 @@ +// A barebones gopher server written in Golang +// This fork uses a config file instead of environment variables +// Copyright: +// (C) 2021 Shokara Kou +// (C) 2023 Izuru Yakumo + +package main + +import ( + "bufio" + "io" + "log" + "net" + "os" + "strings" + "flag" + "gopkg.in/ini.v1" + "marisa.chaotic.ninja/tokiko" +) + +var conf struct { + port string + addr string + hostname string + rootdir string +} + +func parseconfig(file string) error { + cfg, err := ini.Load(file) + if err != nil { + return err + } + conf.port = cfg.Section("").Key("port").String() + conf.addr = cfg.Section("").Key("addr").String() + conf.hostname = cfg.Section("").Key("hostname").String() + conf.rootdir = cfg.Section("").Key("rootdir").String() + + return nil +} + +func formatLine(line string) string { + trimmed := strings.TrimRight(line, "\r\n") + splitted := strings.Split(trimmed, "\t") + + if len(splitted) == 3 { + return line + } else if len(splitted) == 2 { + line += "\t" + conf.addr + "\t" + conf.port + } else if len(splitted) == 1 { + line += "\tErr\t" + conf.hostname + "\t" + conf.port + } + + return line + "\n" +} + +func writeError(c net.Conn, msg string) { + c.Write([]byte(formatLine("3" + msg))) +} + +func printGophermap(c net.Conn, dir string) { + file, err := os.Open(dir + "/Gophermap") + if err != nil { + writeError(c, err.Error()) + log.Println(err) + } + defer func() { + if err = file.Close(); err != nil { + log.Println(err) + } + }() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + c.Write([]byte(formatLine(scanner.Text()) + "\n")) + } + + c.Write([]byte(".\r\n")) +} + +func printFile(c net.Conn, path string) { + file, err := os.Open(path) + if err != nil { + writeError(c, err.Error()) + log.Println(err) + } + defer func() { + if err = file.Close(); err != nil { + log.Println(err) + } + }() + + const bufSz = 1024 + b := make([]byte, bufSz) + + for { + readSz, err := file.Read(b) + if err != nil { + if err != io.EOF { + log.Println(err) + } + break + } + + c.Write(b[:readSz]) + } +} + +func connHandle(c net.Conn) { + data, err := bufio.NewReader(c).ReadString('\n') + if err != nil { + log.Println(err) + return + } + + selector := strings.TrimRight(data, "\r\n") + + if selector == "" { + printGophermap(c, "./") + } else if strings.Contains(selector, "..") { + writeError(c, "Selector contains ..") + } else if selector[0] == '/' { + info, err := os.Stat(selector[1:]) + if err != nil { + writeError(c, err.Error()) + log.Println(err) + + c.Close() + return + } + + if info.IsDir() { + printGophermap(c, selector[1:]) + } else { + printFile(c, selector[1:]) + } + } else { + writeError(c, "Selector doesn't start with a /") + } + + c.Close() +} +func main() { + var configfile string + + flag.StringVar(&configfile, "f", "", "Configuration file") + flag.Parse() + + if configfile != "" { + parseconfig(configfile) + } + + conf.addr = "127.0.0.1" + conf.port = "70" + conf.hostname = "localhost" + conf.rootdir = "/var/gopher" + + LISTEN_ADDR := conf.addr + ":" + conf.port + + log.Printf("Starting tokiko version %v on %s\n", tokiko.FullVersion(), LISTEN_ADDR) + + os.Chdir(conf.rootdir) + + l, err := net.Listen("tcp", LISTEN_ADDR) + if err != nil { + log.Fatal(err) + return + } + defer l.Close() + + for { + c, err := l.Accept() + if err != nil { + log.Println(err) + return + } + + go connHandle(c) + } +}