KEY = host_key
PORT = 2022
-all: $(BINARY)
+SRCS = %.go
-**/*.go:
- go build ./...
+all: $(BINARY)
-$(BINARY): **/*.go *.go
- go build -ldflags "-X main.buildCommit `git describe --long --tags --dirty --always`" .
+$(BINARY): **/**/*.go **/*.go *.go
+ go build -ldflags "-X main.buildCommit `git describe --long --tags --dirty --always`" ./cmd/ssh-chat
deps:
go get .
-package main
+package sshchat
import (
"errors"
// The error returned a key is checked that is banned.
var ErrBanned = errors.New("banned")
-// NewAuthKey returns string from an ssh.PublicKey.
-func NewAuthKey(key ssh.PublicKey) string {
+// newAuthKey returns string from an ssh.PublicKey used to index the key in our lookup.
+func newAuthKey(key ssh.PublicKey) string {
if key == nil {
return ""
}
return sshd.Fingerprint(key)
}
-// NewAuthAddr returns a string from a net.Addr
-func NewAuthAddr(addr net.Addr) string {
+// newAuthAddr returns a string from a net.Addr used to index the address the key in our lookup.
+func newAuthAddr(addr net.Addr) string {
if addr == nil {
return ""
}
return host
}
-// Auth stores fingerprint lookups
-// TODO: Add timed auth by using a time.Time instead of struct{} for values.
+// Auth stores lookups for bans, whitelists, and ops. It implements the sshd.Auth interface.
type Auth struct {
sync.RWMutex
bannedAddr *Set
ops *Set
}
-// NewAuth creates a new default Auth.
+// NewAuth creates a new empty Auth.
func NewAuth() *Auth {
return &Auth{
bannedAddr: NewSet(),
// Check determines if a pubkey fingerprint is permitted.
func (a *Auth) Check(addr net.Addr, key ssh.PublicKey) (bool, error) {
- authkey := NewAuthKey(key)
+ authkey := newAuthKey(key)
if a.whitelist.Len() != 0 {
// Only check whitelist if there is something in it, otherwise it's disabled.
banned := a.banned.In(authkey)
if !banned {
- banned = a.bannedAddr.In(NewAuthAddr(addr))
+ banned = a.bannedAddr.In(newAuthAddr(addr))
}
if banned {
return false, ErrBanned
if key == nil {
return
}
- authkey := NewAuthKey(key)
+ authkey := newAuthKey(key)
if d != 0 {
a.ops.AddExpiring(authkey, d)
} else {
if key == nil {
return false
}
- authkey := NewAuthKey(key)
+ authkey := newAuthKey(key)
return a.ops.In(authkey)
}
if key == nil {
return
}
- authkey := NewAuthKey(key)
+ authkey := newAuthKey(key)
if d != 0 {
a.whitelist.AddExpiring(authkey, d)
} else {
if key == nil {
return
}
- a.BanFingerprint(NewAuthKey(key), d)
+ a.BanFingerprint(newAuthKey(key), d)
}
// BanFingerprint will set a public key fingerprint as banned.
// Ban will set an IP address as banned.
func (a *Auth) BanAddr(addr net.Addr, d time.Duration) {
- key := NewAuthAddr(addr)
+ key := newAuthAddr(addr)
if d != 0 {
a.bannedAddr.AddExpiring(key, d)
} else {
-package main
+package sshchat
import (
"crypto/rand"
"github.com/jessevdk/go-flags"
"golang.org/x/crypto/ssh"
+ "github.com/shazow/ssh-chat"
"github.com/shazow/ssh-chat/chat"
"github.com/shazow/ssh-chat/chat/message"
"github.com/shazow/ssh-chat/sshd"
log.Debug,
}
-var buildCommit string
+func fail(code int, format string, args ...interface{}) {
+ fmt.Fprintf(os.Stderr, format, args...)
+ os.Exit(code)
+}
func main() {
options := Options{}
}
logLevel := logLevels[numVerbose]
- logger = golog.New(os.Stderr, logLevel)
+ sshchat.SetLogger(golog.New(os.Stderr, logLevel))
if logLevel == log.Debug {
// Enable logging from submodules
privateKey, err := ReadPrivateKey(privateKeyPath)
if err != nil {
- logger.Errorf("Couldn't read private key: %v", err)
- os.Exit(2)
+ fail(2, "Couldn't read private key: %v\n", err)
}
signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil {
- logger.Errorf("Failed to parse key: %v", err)
- os.Exit(3)
+ fail(3, "Failed to parse key: %v\n", err)
}
- auth := NewAuth()
+ auth := sshchat.NewAuth()
config := sshd.MakeAuth(auth)
config.AddHostKey(signer)
s, err := sshd.ListenSSH(options.Bind, config)
if err != nil {
- logger.Errorf("Failed to listen on socket: %v", err)
- os.Exit(4)
+ fail(4, "Failed to listen on socket: %v\n", err)
}
defer s.Close()
s.RateLimit = true
fmt.Printf("Listening for connections on %v\n", s.Addr().String())
- host := NewHost(s)
- host.auth = auth
- host.theme = &message.Themes[0]
+ host := sshchat.NewHost(s, auth)
+ host.SetTheme(message.Themes[0])
err = fromFile(options.Admin, func(line []byte) error {
key, _, _, _, err := ssh.ParseAuthorizedKey(line)
return nil
})
if err != nil {
- logger.Errorf("Failed to load admins: %v", err)
- os.Exit(5)
+ fail(5, "Failed to load admins: %v\n", err)
}
err = fromFile(options.Whitelist, func(line []byte) error {
return err
}
auth.Whitelist(key, 0)
- logger.Debugf("Whitelisted: %s", line)
return nil
})
if err != nil {
- logger.Errorf("Failed to load whitelist: %v", err)
- os.Exit(5)
+ fail(6, "Failed to load whitelist: %v\n", err)
}
if options.Motd != "" {
motd, err := ioutil.ReadFile(options.Motd)
if err != nil {
- logger.Errorf("Failed to load MOTD file: %v", err)
- return
+ fail(7, "Failed to load MOTD file: %v\n", err)
}
motdString := strings.TrimSpace(string(motd))
// hack to normalize line endings into \r\n
} else if options.Log != "" {
fp, err := os.OpenFile(options.Log, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
- logger.Errorf("Failed to open log file for writing: %v", err)
- return
+ fail(8, "Failed to open log file for writing: %v", err)
}
host.SetLogging(fp)
}
signal.Notify(sig, os.Interrupt)
<-sig // Wait for ^C signal
- logger.Warningf("Interrupt signal detected, shutting down.")
+ fmt.Fprintln(os.Stderr, "Interrupt signal detected, shutting down.")
os.Exit(0)
}
--- /dev/null
+/*
+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
-package main
+package sshchat
import (
"errors"
"github.com/shazow/ssh-chat/sshd"
)
+var buildCommit string
+
const maxInputLength int = 1024
// GetPrompt will render the terminal prompt string based on the user.
count int
// Default theme
- theme *message.Theme
+ theme message.Theme
}
// 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()
h := Host{
Room: room,
listener: listener,
commands: chat.Commands{},
+ auth: auth,
}
// Make our own commands registry instance.
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.
func (h *Host) SetMotd(motd string) {
h.motd = motd
func (h *Host) Connect(term *sshd.Terminal) {
id := NewIdentity(term.Conn)
user := message.NewUserScreen(id, term)
- user.Config.Theme = h.theme
+ user.Config.Theme = &h.theme
go func() {
// Close term once user is closed.
user.Wait()
-package main
+package sshchat
import (
"bufio"
t.Fatal(err)
}
defer s.Close()
- host := NewHost(s)
+ host := NewHost(s, nil)
go host.Serve()
done := make(chan struct{}, 1)
scanner.Scan()
actual := scanner.Text()
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)
t.Fatal(err)
}
defer s.Close()
- host := NewHost(s)
- host.auth = auth
+ host := NewHost(s, auth)
go host.Serve()
target := s.Addr().String()
}
defer s.Close()
addr := s.Addr().String()
- host := NewHost(s)
+ host := NewHost(s, nil)
go host.Serve()
connected := make(chan struct{})
-package main
+package sshchat
import (
"fmt"
-package main
+package sshchat
import (
"bytes"
var logger *golog.Logger
+func SetLogger(l *golog.Logger) {
+ logger = l
+}
+
func init() {
// Set a default null logger
var b bytes.Buffer
- logger = golog.New(&b, log.Debug)
+ SetLogger(golog.New(&b, log.Debug))
}
-package main
+package sshchat
import (
"sync"
return true
}
-type SetValue interface {
+type setValue interface {
Bool() bool
}
// Set with expire-able keys
type Set struct {
- lookup map[string]SetValue
+ lookup map[string]setValue
sync.Mutex
}
// NewSet creates a new set.
func NewSet() *Set {
return &Set{
- lookup: map[string]SetValue{},
+ lookup: map[string]setValue{},
}
}
-package main
+package sshchat
import (
"testing"