mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-13 07:37:17 +03:00
Root sshchat package, main moved into cmd/ssh-chat/.
This commit is contained in:
parent
76902aea1c
commit
8188deef30
9
Makefile
9
Makefile
@ -2,13 +2,12 @@ BINARY = ssh-chat
|
|||||||
KEY = host_key
|
KEY = host_key
|
||||||
PORT = 2022
|
PORT = 2022
|
||||||
|
|
||||||
|
SRCS = %.go
|
||||||
|
|
||||||
all: $(BINARY)
|
all: $(BINARY)
|
||||||
|
|
||||||
**/*.go:
|
$(BINARY): **/**/*.go **/*.go *.go
|
||||||
go build ./...
|
go build -ldflags "-X main.buildCommit `git describe --long --tags --dirty --always`" ./cmd/ssh-chat
|
||||||
|
|
||||||
$(BINARY): **/*.go *.go
|
|
||||||
go build -ldflags "-X main.buildCommit `git describe --long --tags --dirty --always`" .
|
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
go get .
|
go get .
|
||||||
|
29
auth.go
29
auth.go
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -16,8 +16,8 @@ var ErrNotWhitelisted = errors.New("not whitelisted")
|
|||||||
// The error returned a key is checked that is banned.
|
// The error returned a key is checked that is banned.
|
||||||
var ErrBanned = errors.New("banned")
|
var ErrBanned = errors.New("banned")
|
||||||
|
|
||||||
// NewAuthKey returns string from an ssh.PublicKey.
|
// newAuthKey returns string from an ssh.PublicKey used to index the key in our lookup.
|
||||||
func NewAuthKey(key ssh.PublicKey) string {
|
func newAuthKey(key ssh.PublicKey) string {
|
||||||
if key == nil {
|
if key == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -25,8 +25,8 @@ func NewAuthKey(key ssh.PublicKey) string {
|
|||||||
return sshd.Fingerprint(key)
|
return sshd.Fingerprint(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthAddr returns a string from a net.Addr
|
// newAuthAddr returns a string from a net.Addr used to index the address the key in our lookup.
|
||||||
func NewAuthAddr(addr net.Addr) string {
|
func newAuthAddr(addr net.Addr) string {
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -34,8 +34,7 @@ func NewAuthAddr(addr net.Addr) string {
|
|||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth stores fingerprint lookups
|
// Auth stores lookups for bans, whitelists, and ops. It implements the sshd.Auth interface.
|
||||||
// TODO: Add timed auth by using a time.Time instead of struct{} for values.
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
bannedAddr *Set
|
bannedAddr *Set
|
||||||
@ -44,7 +43,7 @@ type Auth struct {
|
|||||||
ops *Set
|
ops *Set
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuth creates a new default Auth.
|
// NewAuth creates a new empty Auth.
|
||||||
func NewAuth() *Auth {
|
func NewAuth() *Auth {
|
||||||
return &Auth{
|
return &Auth{
|
||||||
bannedAddr: NewSet(),
|
bannedAddr: NewSet(),
|
||||||
@ -61,7 +60,7 @@ func (a Auth) AllowAnonymous() bool {
|
|||||||
|
|
||||||
// Check determines if a pubkey fingerprint is permitted.
|
// Check determines if a pubkey fingerprint is permitted.
|
||||||
func (a *Auth) Check(addr net.Addr, key ssh.PublicKey) (bool, error) {
|
func (a *Auth) Check(addr net.Addr, key ssh.PublicKey) (bool, error) {
|
||||||
authkey := NewAuthKey(key)
|
authkey := newAuthKey(key)
|
||||||
|
|
||||||
if a.whitelist.Len() != 0 {
|
if a.whitelist.Len() != 0 {
|
||||||
// Only check whitelist if there is something in it, otherwise it's disabled.
|
// Only check whitelist if there is something in it, otherwise it's disabled.
|
||||||
@ -74,7 +73,7 @@ func (a *Auth) Check(addr net.Addr, key ssh.PublicKey) (bool, error) {
|
|||||||
|
|
||||||
banned := a.banned.In(authkey)
|
banned := a.banned.In(authkey)
|
||||||
if !banned {
|
if !banned {
|
||||||
banned = a.bannedAddr.In(NewAuthAddr(addr))
|
banned = a.bannedAddr.In(newAuthAddr(addr))
|
||||||
}
|
}
|
||||||
if banned {
|
if banned {
|
||||||
return false, ErrBanned
|
return false, ErrBanned
|
||||||
@ -88,7 +87,7 @@ func (a *Auth) Op(key ssh.PublicKey, d time.Duration) {
|
|||||||
if key == nil {
|
if key == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
authkey := NewAuthKey(key)
|
authkey := newAuthKey(key)
|
||||||
if d != 0 {
|
if d != 0 {
|
||||||
a.ops.AddExpiring(authkey, d)
|
a.ops.AddExpiring(authkey, d)
|
||||||
} else {
|
} else {
|
||||||
@ -102,7 +101,7 @@ func (a *Auth) IsOp(key ssh.PublicKey) bool {
|
|||||||
if key == nil {
|
if key == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
authkey := NewAuthKey(key)
|
authkey := newAuthKey(key)
|
||||||
return a.ops.In(authkey)
|
return a.ops.In(authkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +110,7 @@ func (a *Auth) Whitelist(key ssh.PublicKey, d time.Duration) {
|
|||||||
if key == nil {
|
if key == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
authkey := NewAuthKey(key)
|
authkey := newAuthKey(key)
|
||||||
if d != 0 {
|
if d != 0 {
|
||||||
a.whitelist.AddExpiring(authkey, d)
|
a.whitelist.AddExpiring(authkey, d)
|
||||||
} else {
|
} else {
|
||||||
@ -125,7 +124,7 @@ func (a *Auth) Ban(key ssh.PublicKey, d time.Duration) {
|
|||||||
if key == nil {
|
if key == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.BanFingerprint(NewAuthKey(key), d)
|
a.BanFingerprint(newAuthKey(key), d)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BanFingerprint will set a public key fingerprint as banned.
|
// BanFingerprint will set a public key fingerprint as banned.
|
||||||
@ -140,7 +139,7 @@ func (a *Auth) BanFingerprint(authkey string, d time.Duration) {
|
|||||||
|
|
||||||
// Ban will set an IP address as banned.
|
// Ban will set an IP address as banned.
|
||||||
func (a *Auth) BanAddr(addr net.Addr, d time.Duration) {
|
func (a *Auth) BanAddr(addr net.Addr, d time.Duration) {
|
||||||
key := NewAuthAddr(addr)
|
key := newAuthAddr(addr)
|
||||||
if d != 0 {
|
if d != 0 {
|
||||||
a.bannedAddr.AddExpiring(key, d)
|
a.bannedAddr.AddExpiring(key, d)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/jessevdk/go-flags"
|
"github.com/jessevdk/go-flags"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"github.com/shazow/ssh-chat"
|
||||||
"github.com/shazow/ssh-chat/chat"
|
"github.com/shazow/ssh-chat/chat"
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
"github.com/shazow/ssh-chat/sshd"
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
@ -39,7 +40,10 @@ var logLevels = []log.Level{
|
|||||||
log.Debug,
|
log.Debug,
|
||||||
}
|
}
|
||||||
|
|
||||||
var buildCommit string
|
func fail(code int, format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(os.Stderr, format, args...)
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
options := Options{}
|
options := Options{}
|
||||||
@ -66,7 +70,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logLevel := logLevels[numVerbose]
|
logLevel := logLevels[numVerbose]
|
||||||
logger = golog.New(os.Stderr, logLevel)
|
sshchat.SetLogger(golog.New(os.Stderr, logLevel))
|
||||||
|
|
||||||
if logLevel == log.Debug {
|
if logLevel == log.Debug {
|
||||||
// Enable logging from submodules
|
// Enable logging from submodules
|
||||||
@ -84,33 +88,29 @@ func main() {
|
|||||||
|
|
||||||
privateKey, err := ReadPrivateKey(privateKeyPath)
|
privateKey, err := ReadPrivateKey(privateKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Couldn't read private key: %v", err)
|
fail(2, "Couldn't read private key: %v\n", err)
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signer, err := ssh.ParsePrivateKey(privateKey)
|
signer, err := ssh.ParsePrivateKey(privateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to parse key: %v", err)
|
fail(3, "Failed to parse key: %v\n", err)
|
||||||
os.Exit(3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := NewAuth()
|
auth := sshchat.NewAuth()
|
||||||
config := sshd.MakeAuth(auth)
|
config := sshd.MakeAuth(auth)
|
||||||
config.AddHostKey(signer)
|
config.AddHostKey(signer)
|
||||||
|
|
||||||
s, err := sshd.ListenSSH(options.Bind, config)
|
s, err := sshd.ListenSSH(options.Bind, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to listen on socket: %v", err)
|
fail(4, "Failed to listen on socket: %v\n", err)
|
||||||
os.Exit(4)
|
|
||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
s.RateLimit = true
|
s.RateLimit = true
|
||||||
|
|
||||||
fmt.Printf("Listening for connections on %v\n", s.Addr().String())
|
fmt.Printf("Listening for connections on %v\n", s.Addr().String())
|
||||||
|
|
||||||
host := NewHost(s)
|
host := sshchat.NewHost(s, auth)
|
||||||
host.auth = auth
|
host.SetTheme(message.Themes[0])
|
||||||
host.theme = &message.Themes[0]
|
|
||||||
|
|
||||||
err = fromFile(options.Admin, func(line []byte) error {
|
err = fromFile(options.Admin, func(line []byte) error {
|
||||||
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
|
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
|
||||||
@ -121,8 +121,7 @@ func main() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to load admins: %v", err)
|
fail(5, "Failed to load admins: %v\n", err)
|
||||||
os.Exit(5)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fromFile(options.Whitelist, func(line []byte) error {
|
err = fromFile(options.Whitelist, func(line []byte) error {
|
||||||
@ -131,19 +130,16 @@ func main() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
auth.Whitelist(key, 0)
|
auth.Whitelist(key, 0)
|
||||||
logger.Debugf("Whitelisted: %s", line)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to load whitelist: %v", err)
|
fail(6, "Failed to load whitelist: %v\n", err)
|
||||||
os.Exit(5)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Motd != "" {
|
if options.Motd != "" {
|
||||||
motd, err := ioutil.ReadFile(options.Motd)
|
motd, err := ioutil.ReadFile(options.Motd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to load MOTD file: %v", err)
|
fail(7, "Failed to load MOTD file: %v\n", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
motdString := strings.TrimSpace(string(motd))
|
motdString := strings.TrimSpace(string(motd))
|
||||||
// hack to normalize line endings into \r\n
|
// hack to normalize line endings into \r\n
|
||||||
@ -157,8 +153,7 @@ func main() {
|
|||||||
} else if options.Log != "" {
|
} else if options.Log != "" {
|
||||||
fp, err := os.OpenFile(options.Log, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
fp, err := os.OpenFile(options.Log, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to open log file for writing: %v", err)
|
fail(8, "Failed to open log file for writing: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
host.SetLogging(fp)
|
host.SetLogging(fp)
|
||||||
}
|
}
|
||||||
@ -170,7 +165,7 @@ func main() {
|
|||||||
signal.Notify(sig, os.Interrupt)
|
signal.Notify(sig, os.Interrupt)
|
||||||
|
|
||||||
<-sig // Wait for ^C signal
|
<-sig // Wait for ^C signal
|
||||||
logger.Warningf("Interrupt signal detected, shutting down.")
|
fmt.Fprintln(os.Stderr, "Interrupt signal detected, shutting down.")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
11
godoc.go
Normal file
11
godoc.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
sshchat package is an implementation of an ssh server which serves a chat room
|
||||||
|
instead of a shell.
|
||||||
|
|
||||||
|
sshd subdirectory contains the ssh-related pieces which know nothing about chat.
|
||||||
|
|
||||||
|
chat subdirectory contains the chat-related pieces which know nothing about ssh.
|
||||||
|
|
||||||
|
The Host type is the glue between the sshd and chat pieces.
|
||||||
|
*/
|
||||||
|
package sshchat
|
16
host.go
16
host.go
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -13,6 +13,8 @@ import (
|
|||||||
"github.com/shazow/ssh-chat/sshd"
|
"github.com/shazow/ssh-chat/sshd"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var buildCommit string
|
||||||
|
|
||||||
const maxInputLength int = 1024
|
const maxInputLength int = 1024
|
||||||
|
|
||||||
// GetPrompt will render the terminal prompt string based on the user.
|
// GetPrompt will render the terminal prompt string based on the user.
|
||||||
@ -36,16 +38,17 @@ type Host struct {
|
|||||||
count int
|
count int
|
||||||
|
|
||||||
// Default theme
|
// Default theme
|
||||||
theme *message.Theme
|
theme message.Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHost creates a Host on top of an existing listener.
|
// NewHost creates a Host on top of an existing listener.
|
||||||
func NewHost(listener *sshd.SSHListener) *Host {
|
func NewHost(listener *sshd.SSHListener, auth *Auth) *Host {
|
||||||
room := chat.NewRoom()
|
room := chat.NewRoom()
|
||||||
h := Host{
|
h := Host{
|
||||||
Room: room,
|
Room: room,
|
||||||
listener: listener,
|
listener: listener,
|
||||||
commands: chat.Commands{},
|
commands: chat.Commands{},
|
||||||
|
auth: auth,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make our own commands registry instance.
|
// Make our own commands registry instance.
|
||||||
@ -57,6 +60,11 @@ func NewHost(listener *sshd.SSHListener) *Host {
|
|||||||
return &h
|
return &h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTheme sets the default theme for the host.
|
||||||
|
func (h *Host) SetTheme(theme message.Theme) {
|
||||||
|
h.theme = theme
|
||||||
|
}
|
||||||
|
|
||||||
// SetMotd sets the host's message of the day.
|
// SetMotd sets the host's message of the day.
|
||||||
func (h *Host) SetMotd(motd string) {
|
func (h *Host) SetMotd(motd string) {
|
||||||
h.motd = motd
|
h.motd = motd
|
||||||
@ -74,7 +82,7 @@ func (h Host) isOp(conn sshd.Connection) bool {
|
|||||||
func (h *Host) Connect(term *sshd.Terminal) {
|
func (h *Host) Connect(term *sshd.Terminal) {
|
||||||
id := NewIdentity(term.Conn)
|
id := NewIdentity(term.Conn)
|
||||||
user := message.NewUserScreen(id, term)
|
user := message.NewUserScreen(id, term)
|
||||||
user.Config.Theme = h.theme
|
user.Config.Theme = &h.theme
|
||||||
go func() {
|
go func() {
|
||||||
// Close term once user is closed.
|
// Close term once user is closed.
|
||||||
user.Wait()
|
user.Wait()
|
||||||
|
11
host_test.go
11
host_test.go
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -56,7 +56,7 @@ func TestHostNameCollision(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
host := NewHost(s)
|
host := NewHost(s, nil)
|
||||||
go host.Serve()
|
go host.Serve()
|
||||||
|
|
||||||
done := make(chan struct{}, 1)
|
done := make(chan struct{}, 1)
|
||||||
@ -70,7 +70,7 @@ func TestHostNameCollision(t *testing.T) {
|
|||||||
scanner.Scan()
|
scanner.Scan()
|
||||||
actual := scanner.Text()
|
actual := scanner.Text()
|
||||||
if !strings.HasPrefix(actual, "[foo] ") {
|
if !strings.HasPrefix(actual, "[foo] ") {
|
||||||
t.Errorf("First client failed to get 'foo' name.")
|
t.Errorf("First client failed to get 'foo' name: %q", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual = stripPrompt(actual)
|
actual = stripPrompt(actual)
|
||||||
@ -133,8 +133,7 @@ func TestHostWhitelist(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
host := NewHost(s)
|
host := NewHost(s, auth)
|
||||||
host.auth = auth
|
|
||||||
go host.Serve()
|
go host.Serve()
|
||||||
|
|
||||||
target := s.Addr().String()
|
target := s.Addr().String()
|
||||||
@ -174,7 +173,7 @@ func TestHostKick(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer s.Close()
|
defer s.Close()
|
||||||
addr := s.Addr().String()
|
addr := s.Addr().String()
|
||||||
host := NewHost(s)
|
host := NewHost(s, nil)
|
||||||
go host.Serve()
|
go host.Serve()
|
||||||
|
|
||||||
connected := make(chan struct{})
|
connected := make(chan struct{})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -9,8 +9,12 @@ import (
|
|||||||
|
|
||||||
var logger *golog.Logger
|
var logger *golog.Logger
|
||||||
|
|
||||||
|
func SetLogger(l *golog.Logger) {
|
||||||
|
logger = l
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Set a default null logger
|
// Set a default null logger
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
logger = golog.New(&b, log.Debug)
|
SetLogger(golog.New(&b, log.Debug))
|
||||||
}
|
}
|
||||||
|
8
set.go
8
set.go
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
@ -19,20 +19,20 @@ func (v value) Bool() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type SetValue interface {
|
type setValue interface {
|
||||||
Bool() bool
|
Bool() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set with expire-able keys
|
// Set with expire-able keys
|
||||||
type Set struct {
|
type Set struct {
|
||||||
lookup map[string]SetValue
|
lookup map[string]setValue
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSet creates a new set.
|
// NewSet creates a new set.
|
||||||
func NewSet() *Set {
|
func NewSet() *Set {
|
||||||
return &Set{
|
return &Set{
|
||||||
lookup: map[string]SetValue{},
|
lookup: map[string]setValue{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package sshchat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user