From 73b0f90c1628cf226575ef7585729db21db91f53 Mon Sep 17 00:00:00 2001 From: "Elf M. Sternberg" Date: Wed, 10 Jan 2018 14:27:33 -0800 Subject: [PATCH] COMPLETE: A relatively straightforward implementation of the Monologued server. This is obviously a version 1.0, but it taught me a few things about Go's networking and its batteries included philosophy. I'll have a longer blog post about it in a few days. It still needs a configuration file and a couple of other features found in traditional finger servers, but for now this is a pretty good example. --- dotplan/userpath.go | 43 +++++++++++++++ main.go | 77 ++++++++++++++++++++++++++ monologued/main.go | 7 --- rfc1288/rfc1288.go | 23 ++++---- rfc1288/rfc1288_test.go | 116 +++++++++++++++++++++++++--------------- 5 files changed, 206 insertions(+), 60 deletions(-) create mode 100644 dotplan/userpath.go create mode 100644 main.go delete mode 100644 monologued/main.go diff --git a/dotplan/userpath.go b/dotplan/userpath.go new file mode 100644 index 0000000..9d7867b --- /dev/null +++ b/dotplan/userpath.go @@ -0,0 +1,43 @@ +package dotplan + +import ( + "errors" + "os" + "os/user" + "io/ioutil" + "path" +) + +func GetUserpath(username *string) (error, *string) { + User, err := user.Lookup(*username) + if err != nil { + return err, nil + } + return nil, &User.HomeDir +} + +func GetUserplan(username *string) (error, *[]byte) { + err, Path := GetUserpath(username) + if err != nil { + return err, nil + } + + PlanPath := path.Join(*Path, ".plan") + + Plan, err := os.Stat(PlanPath) + if err != nil { + return err, nil + } + + if Plan.IsDir() { + return errors.New("Not a readable file"), nil + } + + Data, err := ioutil.ReadFile(PlanPath) + if err != nil { + return err, nil + } + + return nil, &Data +} + diff --git a/main.go b/main.go new file mode 100644 index 0000000..bf589f1 --- /dev/null +++ b/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "net" + "time" + "bufio" + "strconv" + "monologued/rfc1288" + "monologued/dotplan" +) + + +const PORT = 2003 + +func main() { + server, err := net.Listen("tcp", ":" + strconv.Itoa(PORT)) + if server == nil { + if (err != nil) { + panic("couldn't start listening: " + err.Error()) + } + panic("Couldn't start listening. Error undefined") + } + for { + client, err := server.Accept() + if client == nil { + fmt.Printf("Connection request failed: %s\n", err.Error()) + continue + } + go Response(client) + } +} + + +func Response(socket net.Conn) { + defer func() { + socket.Close() + }() + + timeout := time.Second * 15 + socket.SetDeadline(time.Now().Add(timeout)) + + buffer := bufio.NewReader(socket) + line, _, err := buffer.ReadLine() + + if err != nil { + socket.Write([]byte("400 Bad Request\r\n\r\n")) + return + } + + err, Request := rfc1288.ParseRfc1288Request(string(line)) + if err != nil { + socket.Write([]byte("400 Bad Request\r\n\r\n")) + return + } + + if Request.Type == rfc1288.Remote { + socket.Write([]byte("403 Forbidden - This server does not support remote requests\r\n\r\n")) + return + } + + if Request.Type == rfc1288.UserList { + socket.Write([]byte("403 Forbidden - This server does not support user lists\r\n\r\n")) + return + } + + err, Data := dotplan.GetUserplan(Request.User) + if err != nil { + socket.Write([]byte(fmt.Sprintf("404 Not Found - No information for user '%s' found\r\n\r\n", *Request.User))) + return + } + + socket.Write(*Data) +} + + + diff --git a/monologued/main.go b/monologued/main.go deleted file mode 100644 index d9685e6..0000000 --- a/monologued/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("What the fuck, over?", extraNonsense()) -} diff --git a/rfc1288/rfc1288.go b/rfc1288/rfc1288.go index b548271..7cd91dc 100644 --- a/rfc1288/rfc1288.go +++ b/rfc1288/rfc1288.go @@ -1,8 +1,9 @@ package rfc1288 import( + "errors" + "strings" "bytes" -// "fmt" ) func is_unix_conventional(c byte) bool { @@ -37,19 +38,23 @@ type Rfc1288Request struct { Host* string } -func parse_rfc1288_request(Buffer string) (Rfc1288ErrorCode, *Rfc1288Request) { +func ParseRfc1288Request(Buffer string) (error, *Rfc1288Request) { + if pos := strings.IndexAny(Buffer, "\r\n"); pos > 0 { + Buffer = Buffer[:pos] + } + Buflen := len(Buffer) if Buflen < 2 { - return BadProtocol, nil + return errors.New("Protocol not recognized"), nil } if Buffer[0] != '/' || (Buffer[1] != 'W' && Buffer[1] != 'w') { - return BadProtocol, nil + return errors.New("Protocol not recognized"), nil } if len(Buffer) == 2 { - return Ok, &Rfc1288Request{UserList, nil, nil} + return nil, &Rfc1288Request{UserList, nil, nil} } index := 2 @@ -58,7 +63,7 @@ func parse_rfc1288_request(Buffer string) (Rfc1288ErrorCode, *Rfc1288Request) { } if Buflen == index { - return Ok, &Rfc1288Request{Type: UserList, User: nil, Host: nil} + return nil, &Rfc1288Request{Type: UserList, User: nil, Host: nil} } user := bytes.NewBufferString("") @@ -71,11 +76,11 @@ func parse_rfc1288_request(Buffer string) (Rfc1288ErrorCode, *Rfc1288Request) { if index == Buflen || (index < Buflen && Buffer[index] == ' ') { ruser := user.String() - return Ok, &Rfc1288Request{Type: User, User: &ruser, Host: nil} + return nil, &Rfc1288Request{Type: User, User: &ruser, Host: nil} } if Buffer[index] != '@' { - return BadRequest, nil + return errors.New("Protocol does not meet specification"), nil } index += 1 @@ -86,5 +91,5 @@ func parse_rfc1288_request(Buffer string) (Rfc1288ErrorCode, *Rfc1288Request) { ruser := user.String() rhost := host.String() - return Ok, &Rfc1288Request{Type: Remote, User: &ruser, Host: &rhost} + return nil, &Rfc1288Request{Type: Remote, User: &ruser, Host: &rhost} } diff --git a/rfc1288/rfc1288_test.go b/rfc1288/rfc1288_test.go index 12af656..e45a9ed 100644 --- a/rfc1288/rfc1288_test.go +++ b/rfc1288/rfc1288_test.go @@ -1,77 +1,105 @@ package rfc1288 -import "testing" +import ( + "fmt" + "path/filepath" + "runtime" + "reflect" + "testing" +) + +// assert fails the test if the condition is false. +func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { + if !condition { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("%s:%d: "+msg+"\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) + tb.FailNow() + } +} + +// ok fails the test if an err is not nil. +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} + +// equals fails the test if exp is not equal to act. +func equals(tb testing.TB, exp, act interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +} func TestGood_List(t *testing.T) { res, req := parse_rfc1288_request("/W") - if res != Ok { - t.Error("Expected a good return") - } - if req.Type != UserList { - t.Error("Expected UserList as a return type") - } + assert(t, res == nil, "Expected result to be nil.") + assert(t, req.Type == UserList, "Expected type to be Userlist") } func TestGood_ListWSpaces(t *testing.T) { res, req := parse_rfc1288_request("/W "); - if res != Ok { - t.Error("Expected a good return") - } - if req.Type != UserList { - t.Error("Expected UserList as a return type") - } + assert(t, res == nil, "Expected result to be nil.") + assert(t, req.Type == UserList, "Expected type to be Userlist") } func TestBad_Start(t *testing.T) { res, _ := parse_rfc1288_request("") - if res != BadProtocol { - t.Error("Expected BadProtocol") - } + assert(t, res != nil, "Expected result to be BadProtocol.") } func TestBad_Start1(t *testing.T) { res, _ := parse_rfc1288_request("/") - if res != BadProtocol { - t.Error("Expected BadProtocol") - } + assert(t, res != nil, "Expected result to be BadProtocol.") } func TestBad_Start2(t *testing.T) { res, _ := parse_rfc1288_request("/X") - if res != BadProtocol { - t.Error("Expected BadProtocol, got", res) - } + assert(t, res != nil, "Expected result to be BadProtocol.") } func TestGood_Name(t *testing.T) { res, req := parse_rfc1288_request("/W foozle") - if res != Ok { - t.Error("Expected a good return") - } - if req.Type != User { - t.Error("Expected User as a return type") - } - if *req.User != "foozle" { - t.Error("The user name did not match passed in value.") - } + assert(t, res == nil, "Expected a good return") + assert(t, req.Type == User, "Expected User as a return type") + assert(t, *req.User == "foozle", "The user name did not match passed in value.") +} + +func TestGood_NameLf(t *testing.T) { + res, req := parse_rfc1288_request("/W foozle\n") + assert(t, res == nil, "Expected a good return") + assert(t, req.Type == User, "Expected User as a return type") + assert(t, *req.User == "foozle", "The user name did not match passed in value.") +} + +func TestGood_NameCr(t *testing.T) { + res, req := parse_rfc1288_request("/W foozle\r") + assert(t, res == nil, "Expected a good return") + assert(t, req.Type == User, "Expected User as a return type") + assert(t, *req.User == "foozle", "The user name did not match passed in value.") +} + +func TestGood_NameCrLf(t *testing.T) { + res, req := parse_rfc1288_request("/W foozle\r\n") + assert(t, res == nil, "Expected a good return") + assert(t, req.Type == User, "Expected User as a return type") + assert(t, *req.User == "foozle", "The user name did not match passed in value.") } func TestGood_NameExtraSpace(t *testing.T) { res, req := parse_rfc1288_request("/W foozle ") - if res != Ok { - t.Error("Expected a good return") - } - if req.Type != User { - t.Error("Expected User as a return type") - } - if *req.User != "foozle" { - t.Error("The user name did not match passed in value.") - } + assert(t, res == nil, "Expected result to be nil.") + assert(t, req.Type == User, "Expected type to be User") + assert(t, *req.User == "foozle", "User name returned did not match") } func TestGood_NameWHost(t *testing.T) { res, req := parse_rfc1288_request("/W foozle@localhost") - if res != Ok { + if res != nil { t.Error("Expected a good return") } if req.Type != Remote { @@ -87,7 +115,7 @@ func TestGood_NameWHost(t *testing.T) { func TestGood_NameWHostAndSpaces(t *testing.T) { res, req := parse_rfc1288_request("/W foozle@localhost ") - if res != Ok { + if res != nil { t.Error("Expected a good return") } if req.Type != Remote { @@ -103,7 +131,7 @@ func TestGood_NameWHostAndSpaces(t *testing.T) { func TestGood_NameWHostAndSpacesAndLowerW(t *testing.T) { res, req := parse_rfc1288_request("/w foozle@localhost ") - if res != Ok { + if res != nil { t.Error("Expected a good return") } if req.Type != Remote { @@ -119,7 +147,7 @@ func TestGood_NameWHostAndSpacesAndLowerW(t *testing.T) { func TestBad_Name(t *testing.T) { res, _ := parse_rfc1288_request("/W foozle.. ") - if res != BadRequest { + if res == nil { t.Error("Expected BadRequest") } }