gBBS/internal/ssh/ssh.go

242 lines
5.8 KiB
Go

package ssh
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"io"
"log"
"net"
"strings"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/term"
"gbbs/internal/config"
"gbbs/internal/messageboard"
"gbbs/internal/prompt"
"gbbs/internal/user"
)
func generateSSHKey() (ssh.Signer, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
return ssh.NewSignerFromKey(key)
}
func Serve(cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) error {
signer, err := generateSSHKey()
if err != nil {
return fmt.Errorf("failed to generate SSH key: %v", err)
}
config := &ssh.ServerConfig{
NoClientAuth: true,
}
config.AddHostKey(signer)
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.SSHPort))
if err != nil {
return err
}
log.Printf("SSH server listening on port %d", cfg.SSHPort)
for {
conn, err := listener.Accept()
if err != nil {
log.Printf("Failed to accept incoming connection: %v", err)
continue
}
go handleConnection(conn, config, cfg, userManager, messageBoard)
}
}
func handleConnection(conn net.Conn, config *ssh.ServerConfig, cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) {
defer conn.Close()
sshConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
log.Printf("Failed to handshake: %v", err)
return
}
defer sshConn.Close()
log.Printf("New SSH connection from %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
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 {
log.Printf("Could not accept channel: %v", err)
continue
}
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
switch req.Type {
case "shell":
ok = true
if len(req.Payload) > 0 {
ok = false
}
case "pty-req":
ok = true
}
req.Reply(ok, nil)
}
}(requests)
term := term.NewTerminal(channel, "> ")
go handleSSHSession(term, cfg, userManager, messageBoard)
}
}
func handleSSHSession(term *term.Terminal, cfg *config.Config, userManager *user.Manager, messageBoard *messageboard.MessageBoard) {
defer term.Write([]byte("Goodbye!\n"))
welcomeScreen, err := prompt.ReadWelcomeScreen(cfg)
if err != nil {
fmt.Fprintf(term, "Error reading welcome screen: %v\n", err)
return
}
term.Write([]byte(welcomeScreen))
term.Write([]byte("\n\n\n\n\n")) // new lines to make it look real nice yo
for {
term.SetPrompt("\033[0;32mChoose (L)ogin or (R)egister: \033[0m")
choice, err := term.ReadLine()
if err != nil {
if err == io.EOF {
return
}
fmt.Fprintf(term, "\033[0;31mError reading input: %v\033[0m\n", err)
continue
}
switch strings.ToLower(strings.TrimSpace(choice)) {
case "l":
username, err := login(term, userManager)
if err != nil {
fmt.Fprintf(term, "\033[0;31mLogin failed: %v\033[0m\n", err)
continue
}
fmt.Fprintf(term, "\n\033[1;32mLogin successful! Welcome, %s!\033[0m\n", username)
time.Sleep(2 * time.Second)
handleBBS(term, username, messageBoard)
return
case "r":
username, err := register(term, userManager)
if err != nil {
fmt.Fprintf(term, "\033[0;31mRegistration failed: %v\033[0m\n", err)
continue
}
fmt.Fprintf(term, "\n\033[1;32mRegistration successful! Welcome, %s!\033[0m\n", username)
time.Sleep(2 * time.Second)
handleBBS(term, username, messageBoard)
return
default:
fmt.Fprintf(term, "\033[0;31mInvalid choice. Please enter 'L' or 'R'.\033[0m\n")
}
}
}
func login(term *term.Terminal, userManager *user.Manager) (string, error) {
term.SetPrompt("Username: ")
username, err := term.ReadLine()
if err != nil {
return "", err
}
term.SetPrompt("Password: ")
password, err := term.ReadPassword("Password: ")
if err != nil {
return "", err
}
authenticated, err := userManager.Authenticate(username, password)
if err != nil {
return "", err
}
if !authenticated {
return "", fmt.Errorf("invalid username or password")
}
return username, nil
}
func register(term *term.Terminal, userManager *user.Manager) (string, error) {
term.SetPrompt("Choose a username: ")
username, err := term.ReadLine()
if err != nil {
return "", err
}
term.SetPrompt("Choose a password: ")
password, err := term.ReadPassword("Choose a password: ")
if err != nil {
return "", err
}
err = userManager.CreateUser(username, password)
if err != nil {
return "", err
}
return username, nil
}
func handleBBS(term *term.Terminal, username string, messageBoard *messageboard.MessageBoard) {
for {
term.Write([]byte("\n\033[0;36mBBS Menu:\033[0m\n"))
term.Write([]byte("1. Read messages\n"))
term.Write([]byte("2. Post message\n"))
term.Write([]byte("3. Logout\n"))
term.SetPrompt("Choice: ")
choice, err := term.ReadLine()
if err != nil {
fmt.Fprintf(term, "\033[0;31mError reading input: %v\033[0m\n", err)
continue
}
switch strings.TrimSpace(choice) {
case "1":
messages, err := messageBoard.GetMessages()
if err != nil {
fmt.Fprintf(term, "\033[0;31mError reading messages: %v\033[0m\n", err)
} else {
for _, msg := range messages {
fmt.Fprintf(term, "%s\n", msg)
}
}
case "2":
term.SetPrompt("Enter your message: ")
message, err := term.ReadLine()
if err != nil {
fmt.Fprintf(term, "\033[0;31mError reading message: %v\033[0m\n", err)
continue
}
err = messageBoard.PostMessage(username, message)
if err != nil {
fmt.Fprintf(term, "\033[0;31mError posting message: %v\033[0m\n", err)
} else {
fmt.Fprintf(term, "\033[0;32mMessage posted successfully!\033[0m\n")
}
case "3":
return
default:
fmt.Fprintf(term, "\033[0;31mInvalid choice. Please try again.\033[0m\n")
}
}
}