119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
package main
|
|
|
|
// Copyright (c) 2024 Julian Müller (ChaoticByte)
|
|
// License: MIT
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/gliderlabs/ssh"
|
|
)
|
|
|
|
var sessionListMutex = &sync.Mutex{}
|
|
var sessionList = []*ssh.Session{}
|
|
|
|
func PubkeyAuth(ctx ssh.Context, key ssh.PublicKey) bool {
|
|
// verify public key for auth
|
|
u := ctx.User()
|
|
pubkey := config.Clients[u]
|
|
if pubkey == "" { // username not in config.Clients
|
|
log.Printf("Authentication failure: Unknown user %s", u)
|
|
return false
|
|
}
|
|
k, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubkey))
|
|
if err != nil {
|
|
log.Printf("Authentication failure: Could not parse public key for %s", u)
|
|
}
|
|
if ssh.KeysEqual(k, key) {
|
|
return true // bassd
|
|
} else {
|
|
log.Printf("Authentication failure: Invalid public key for user %s", u)
|
|
return false
|
|
}
|
|
}
|
|
|
|
func AddToSessionList(s *ssh.Session) int {
|
|
// add new session to list
|
|
var idx int = -1
|
|
sessionListMutex.Lock()
|
|
defer sessionListMutex.Unlock()
|
|
lenClientList := len(sessionList)
|
|
// try to reuse an existing nil position in the slice
|
|
for i := range(lenClientList) {
|
|
if sessionList[i] == nil {
|
|
// we found one!
|
|
sessionList[i] = s
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx == -1 {
|
|
// we have to append
|
|
sessionList = append(sessionList, s)
|
|
idx = lenClientList
|
|
}
|
|
return idx
|
|
}
|
|
|
|
func RemoveFromSessionList(idx int) {
|
|
sessionListMutex.Lock()
|
|
defer sessionListMutex.Unlock()
|
|
// we have to set it to nil instead of constructing a new
|
|
// slice, so that the other session handlers have still
|
|
// the correct index for their own removal
|
|
sessionList[idx] = nil // session associated with the pointer reference should be freed automatically
|
|
}
|
|
|
|
func BroadcastLine(line []byte) {
|
|
if logFlag { log.Printf("%s", line) } // output message
|
|
sessions := sessionList // create snapshot of client list
|
|
for i := range(len(sessions)) {
|
|
session := sessions[i]
|
|
if session != nil {
|
|
(*session).Write(line)
|
|
}
|
|
}
|
|
}
|
|
|
|
func HandleSession(session ssh.Session) {
|
|
// get username
|
|
user := session.Context().User()
|
|
msgPrefix := []byte(fmt.Sprintf("%s: ", user))
|
|
// Add client to list
|
|
sessionListIdx := AddToSessionList(&session)
|
|
defer func () { // cleanup tasks
|
|
RemoveFromSessionList(sessionListIdx)
|
|
log.Printf("User %s disconnected.\n", user)
|
|
BroadcastLine([]byte(fmt.Sprintf("[disconnected] %s\n", user)))
|
|
}()
|
|
// helo
|
|
log.Printf("User %s connected.\n", user)
|
|
BroadcastLine([]byte(fmt.Sprintf("[connected] %s\n", user)))
|
|
// IO
|
|
linebuf := bufio.NewReader(session)
|
|
for {
|
|
line, err := linebuf.ReadBytes('\n')
|
|
if err != nil {
|
|
if err.Error() != "EOF" {
|
|
log.Println("An error occurred! ", user, " ", err)
|
|
}
|
|
break // disconnect
|
|
} else {
|
|
BroadcastLine(slices.Concat(msgPrefix, line))
|
|
}
|
|
}
|
|
}
|
|
|
|
func RunServer() {
|
|
log.Printf("Starting SSH server on %s:%v", config.Host, config.Port)
|
|
log.Fatal(ssh.ListenAndServe(
|
|
fmt.Sprintf("%s:%v", config.Host, config.Port),
|
|
HandleSession,
|
|
ssh.HostKeyFile(privateKeyFilepath),
|
|
ssh.PublicKeyAuth(PubkeyAuth),
|
|
ssh.NoPty()))
|
|
}
|