Skip to content

Commit

Permalink
slight refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
donuts-are-good committed Apr 1, 2023
1 parent 06be8dd commit c4989bb
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 146 deletions.
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MIT License
Copyright (c) [year] [fullname]
Copyright (c) 2023 donuts-are-good https://github.com/donuts-are-good
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
Expand Down
298 changes: 153 additions & 145 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"os"
"strconv"
"strings"
"sync"
"unicode"

"github.com/jmoiron/sqlx"
Expand All @@ -22,57 +21,107 @@ import (
"golang.org/x/term"
)

var users = make(map[string]*user)
var usersMutex sync.Mutex
var messageCache *list.List
func init() {
messageCache = list.New()
}
func main() {
db := initSqliteDB()
defer db.Close()

initBoardSchema(db)

adminDB := initAdminDB()
if adminDB == nil {
return
}
defer adminDB.Close()

initAdminSchema(adminDB)

func initSqliteDB() *sqlx.DB {
db, err := sqlx.Connect("sqlite3", "board.db")
go adminAPI()

var privateKeyPath string
flag.StringVar(&privateKeyPath, "key", "./keys/ssh_host_ed25519_key", "Path to the private key")
flag.Parse()
if _, err := os.Stat("./keys"); os.IsNotExist(err) {
fmt.Println("Error: ./keys directory does not exist. Please create it and generate an ed25519 keypair.")
return
}
if _, err := os.Stat(privateKeyPath); os.IsNotExist(err) {
fmt.Printf("Error: private key file %s does not exist. Please generate an ed25519 keypair.\n", privateKeyPath)
return
}
users = make(map[string]*user)
if len(os.Args) != 2 {
fmt.Printf("Usage: %s <port>\n", os.Args[0])
return
}
config, err := configureSSHServer(privateKeyPath)
if err != nil {
log.Fatalln(err)
fmt.Println("Error configuring SSH server:", err.Error())
return
}
return db
}

func initBoardSchema(db *sqlx.DB) {
schema := `
CREATE TABLE IF NOT EXISTS discussions (
id INTEGER PRIMARY KEY,
author TEXT NOT NULL,
message TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS replies (
id INTEGER PRIMARY KEY,
discussion_id INTEGER NOT NULL,
author TEXT NOT NULL,
message TEXT NOT NULL,
FOREIGN KEY (discussion_id) REFERENCES discussions(id) ON DELETE CASCADE
);
`
_, err := db.Exec(schema)
listener, err := net.Listen("tcp", ":"+os.Args[1])
if err != nil {
log.Fatalln(err)
fmt.Println("Error listening:", err.Error())
return
}
defer listener.Close()
fmt.Println("Listening on :" + os.Args[1])
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting:", err.Error())
continue
}
go func(conn net.Conn) {
defer conn.Close()
sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
fmt.Println("Error upgrading connection to SSH:", err.Error())
return
}
defer sshConn.Close()
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
fmt.Println("Error accepting channel:", err.Error())
return
}
go handleConnection(db, channel, sshConn, requests)
}
}(conn)
}
}

type user struct {
Pubkey string `json:"pubkey" db:"pubkey"`
Hash string `json:"hash" db:"hash"`
Conn ssh.Channel `json:"-"`
Ignored map[string]bool `json:"-"`
func generateHash(pubkey string) string {
h := sha3.NewShake256()
h.Write([]byte(pubkey))
checksum := make([]byte, 16)
h.Read(checksum)
return base64.StdEncoding.EncodeToString(checksum)
}

type discussion struct {
ID int `json:"id" db:"id"`
Author string `json:"author" db:"author"`
Message string `json:"message" db:"message"`
Replies []*reply `json:"replies"`
func disconnect(hash string) {
removeUser(hash)
}

type reply struct {
Author string `json:"author" db:"author"`
Message string `json:"message" db:"message"`
func broadcast(message string) {
addToCache(message)
log.Println("msg len: ", len(message))
log.Println("msg txt: ", message)
sender := message[1:9]
for _, user := range getAllUsers() {
if _, ignored := user.Ignored[sender]; !ignored {
fmt.Fprintln(user.Conn, "\r\n"+message)
}
}
}

func addDiscussion(db *sqlx.DB, author, message string) int {
Expand Down Expand Up @@ -134,10 +183,6 @@ func listReplies(db *sqlx.DB, postNumber int, term *term.Terminal) {
}
}

func init() {
messageCache = list.New()
}

func addToCache(message string) {
messageCache.PushBack(message)
if messageCache.Len() > 100 {
Expand Down Expand Up @@ -197,96 +242,6 @@ func getAllUsers() []*user {
return allUsers
}

func main() {
db := initSqliteDB()
defer db.Close()

initBoardSchema(db)

var privateKeyPath string
flag.StringVar(&privateKeyPath, "key", "./keys/ssh_host_ed25519_key", "Path to the private key")
flag.Parse()
if _, err := os.Stat("./keys"); os.IsNotExist(err) {
fmt.Println("Error: ./keys directory does not exist. Please create it and generate an ed25519 keypair.")
return
}
if _, err := os.Stat(privateKeyPath); os.IsNotExist(err) {
fmt.Printf("Error: private key file %s does not exist. Please generate an ed25519 keypair.\n", privateKeyPath)
return
}
users = make(map[string]*user)
if len(os.Args) != 2 {
fmt.Printf("Usage: %s <port>\n", os.Args[0])
return
}
config, err := configureSSHServer(privateKeyPath)
if err != nil {
fmt.Println("Error configuring SSH server:", err.Error())
return
}

listener, err := net.Listen("tcp", ":"+os.Args[1])
if err != nil {
fmt.Println("Error listening:", err.Error())
return
}
defer listener.Close()
fmt.Println("Listening on :" + os.Args[1])
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting:", err.Error())
continue
}
go func(conn net.Conn) {
defer conn.Close()
sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
fmt.Println("Error upgrading connection to SSH:", err.Error())
return
}
defer sshConn.Close()
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
fmt.Println("Error accepting channel:", err.Error())
return
}
go handleConnection(db, channel, sshConn, requests)
}
}(conn)
}
}

func generateHash(pubkey string) string {
h := sha3.NewShake256()
h.Write([]byte(pubkey))
checksum := make([]byte, 16)
h.Read(checksum)
return base64.StdEncoding.EncodeToString(checksum)
}

func disconnect(hash string) {
removeUser(hash)
}

func broadcast(message string) {
addToCache(message)
log.Println("msg len: ", len(message))
log.Println("msg txt: ", message)
sender := message[1:9]
for _, user := range getAllUsers() {
if _, ignored := user.Ignored[sender]; !ignored {
fmt.Fprintln(user.Conn, "\r\n"+message)
}
}
}

func cleanString(dirtyString string) (string, error) {
var clean strings.Builder
for _, r := range dirtyString {
Expand Down Expand Up @@ -373,6 +328,10 @@ makeUsername:
}
} else if strings.HasPrefix(input, "/help") {
writeHelpMenu(term)
} else if strings.HasPrefix(input, "/license") {
writeLicenseProse(term)
} else if strings.HasPrefix(input, "/version") {
writeVersionInfo(term)
} else if strings.HasPrefix(input, "/users") {
writeUsersOnline(term)
} else if strings.HasPrefix(input, "/pubkey") {
Expand Down Expand Up @@ -444,7 +403,7 @@ I8[ "" BBP' "8a BBP' "8a BBP' "8a BBP' "8a BBP' "8a
'"Y8ba, BB BB BB BB BB BB BB d8 BB d8
aa ]8I BB BB BB BB BB BB BBb, ,a8" BBb, ,a8"
'"YbbdP"' BB BB BB BB BB BB 8Y"Ybbd8"' 8Y"Ybbd8"' BBS
> MIT 2023, https://github.com/donuts-are-good/shhhbb v.0.1.2
> MIT 2023, https://github.com/donuts-are-good/shhhbb ` + semverInfo + `
[RULES] [GOALS]
- your words are your own - a space for hackers & devs
Expand All @@ -466,14 +425,63 @@ func writeUsersOnline(term *term.Terminal) {
}
}
func writeHelpMenu(term *term.Terminal) {
term.Write([]byte("Available commands:\n" +
"/help\t- show this help message\n" +
"/pubkey\t- show your pubkey hash\n" +
"/users\t- list all connected users\n" +
"/message <user hash> <body>\t- send a direct message to a user\n\n" +
"Message Board:\n" +
"/post <message>\t- post a new discussion\n" +
"/list\t- list all discussions\n" +
"/replies <post number>\t- list all replies to a discussion\n" +
"/reply <post number> <reply body>\t- reply to a discussion\n"))
term.Write([]byte(`
[General Commands]
/help
- show this help message
/pubkey
- show your pubkey hash, which is also your username
/users
- list all online users
/message <user hash> <body>
- ex: /message @A1Gla593 hey there friend
- send a direct message to a user
[Message Board]
/post <message>
- ex: /post this is my cool title
- posts a new discussion topic
/list
- list all discussions
/replies <post number>
- ex: /replies 1
- list all replies to a discussion
/reply <post number> <reply body>
- ex: /reply 1 hello everyone
- reply to a discussion
[Misc. Commands]
/license
- display the license text for shhhbb
/version
- display the shhhbb version information
`))
}
func writeLicenseProse(term *term.Terminal) {
term.Write([]byte(`
MIT License
Copyright (c) 2023 donuts-are-good https://github.com/donuts-are-good
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
`))
}
func writeVersionInfo(term *term.Terminal) {
term.Write([]byte(`
shhhbb bbs ` + semverInfo + `
MIT License 2023 donuts-are-good
https://github.com/donuts-are-good/shhhbb
`))
}

0 comments on commit c4989bb

Please sign in to comment.