Add project files
This commit is contained in:
parent
d93b571440
commit
2dd8f72714
9 changed files with 260 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -20,3 +20,6 @@
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
go.work.sum
|
go.work.sum
|
||||||
|
|
||||||
|
# build
|
||||||
|
ass
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 Julian Müller
|
Copyright (c) 2024 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
30
args.go
Normal file
30
args.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Copyright (c) 2024 Julian Müller (ChaoticByte)
|
||||||
|
// License: MIT
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configFilepath string
|
||||||
|
var privateKeyFilepath string
|
||||||
|
var logFlag bool
|
||||||
|
|
||||||
|
func ParseCommandline() bool {
|
||||||
|
flag.StringVar(&configFilepath, "config", "", "The path to the config file (required)")
|
||||||
|
flag.StringVar(&privateKeyFilepath, "pkey", "", "The path to the private key file (required)")
|
||||||
|
flag.BoolVar(&logFlag, "log", false, "Enable logging of messages")
|
||||||
|
flag.Parse()
|
||||||
|
missing := false
|
||||||
|
if configFilepath == "" {
|
||||||
|
missing = true
|
||||||
|
}
|
||||||
|
if privateKeyFilepath == "" {
|
||||||
|
missing = true
|
||||||
|
}
|
||||||
|
if missing {
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
return !missing
|
||||||
|
}
|
8
config.example.yml
Normal file
8
config.example.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Example server config file
|
||||||
|
host: ''
|
||||||
|
port: 8022
|
||||||
|
clients:
|
||||||
|
# This are just examples. Please generate your own keypair for each client you want to add.
|
||||||
|
# username: public-key
|
||||||
|
example1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEg9AzPuPe/zv0HszL+aRdJgZ8DPwVLV59FJoUHe7kWH
|
||||||
|
example2: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQH6ESwEHVvlbxZJd7JhTPWXy3yQ5Zaf8qVBxzf6Td/
|
24
config.go
Normal file
24
config.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Copyright (c) 2024 Julian Müller (ChaoticByte)
|
||||||
|
// License: MIT
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Clients map[string]string `yaml:"clients"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseConfig(filepath string) {
|
||||||
|
data, err := os.ReadFile(filepath)
|
||||||
|
if err != nil { log.Fatal(err) }
|
||||||
|
err = yaml.Unmarshal(data, &config)
|
||||||
|
if err != nil { log.Fatal(err) }
|
||||||
|
}
|
17
go.mod
Normal file
17
go.mod
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
module github.com/ChaoticByte/ass
|
||||||
|
go 1.22.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gliderlabs/ssh v0.3.7
|
||||||
|
github.com/goccy/go-yaml v1.11.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||||
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
golang.org/x/crypto v0.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||||
|
)
|
43
go.sum
Normal file
43
go.sum
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
|
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||||
|
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||||
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||||
|
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
|
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||||
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
|
github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I=
|
||||||
|
github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||||
|
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||||
|
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
15
main.go
Normal file
15
main.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Copyright (c) 2024 Julian Müller (ChaoticByte)
|
||||||
|
// License: MIT
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if ParseCommandline() {
|
||||||
|
ParseConfig(configFilepath)
|
||||||
|
RunServer()
|
||||||
|
} else {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
119
ssh.go
Normal file
119
ssh.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
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()))
|
||||||
|
}
|
Reference in a new issue