mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-06-06 02:13:07 +03:00
Merge branch 'refactor' of github.com:shazow/ssh-chat into refactor
This commit is contained in:
commit
7a39898b36
@ -26,7 +26,7 @@ Usage:
|
||||
Application Options:
|
||||
-v, --verbose Show verbose logging.
|
||||
-i, --identity= Private key to identify server with. (~/.ssh/id_rsa)
|
||||
--bind= Host and port to listen on. (0.0.0.0:22)
|
||||
--bind= Host and port to listen on. (0.0.0.0:2022)
|
||||
--admin= Fingerprint of pubkey to mark as admin.
|
||||
--whitelist= Optional file of pubkey fingerprints that are allowed to connect
|
||||
--motd= Message of the Day file (optional)
|
||||
@ -40,7 +40,7 @@ After doing `go get github.com/shazow/ssh-chat` on this repo, you should be able
|
||||
to run a command like:
|
||||
|
||||
```
|
||||
$ ssh-chat --verbose --bind ":2022" --identity ~/.ssh/id_dsa
|
||||
$ ssh-chat --verbose --bind ":22" --identity ~/.ssh/id_dsa
|
||||
```
|
||||
|
||||
To bind on port 22, you'll need to make sure it's free (move any other ssh
|
||||
|
9
auth.go
9
auth.go
@ -17,6 +17,15 @@ type Auth struct {
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewAuth creates a new default Auth.
|
||||
func NewAuth() Auth {
|
||||
return Auth{
|
||||
whitelist: make(map[string]struct{}),
|
||||
banned: make(map[string]struct{}),
|
||||
ops: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// AllowAnonymous determines if anonymous users are permitted.
|
||||
func (a Auth) AllowAnonymous() bool {
|
||||
a.RLock()
|
||||
|
@ -85,6 +85,12 @@ func (ch *Channel) HandleMsg(m Message) {
|
||||
// Skip
|
||||
return
|
||||
}
|
||||
if _, ok := m.(*AnnounceMsg); ok {
|
||||
if user.Config.Quiet {
|
||||
// Skip
|
||||
return
|
||||
}
|
||||
}
|
||||
err := user.Send(m)
|
||||
if err != nil {
|
||||
ch.Leave(user)
|
||||
@ -168,7 +174,7 @@ func (ch *Channel) NamesPrefix(prefix string) []string {
|
||||
members := ch.members.ListPrefix(prefix)
|
||||
names := make([]string, len(members))
|
||||
for i, u := range members {
|
||||
names[i] = u.(*User).Name()
|
||||
names[i] = u.(*Member).User.Name()
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
@ -56,3 +56,137 @@ func TestChannelJoin(t *testing.T) {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelDoesntBroadcastAnnounceMessagesWhenQuiet(t *testing.T) {
|
||||
u := NewUser("foo")
|
||||
u.Config = UserConfig{
|
||||
Quiet: true,
|
||||
}
|
||||
|
||||
ch := NewChannel()
|
||||
defer ch.Close()
|
||||
|
||||
err := ch.Join(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Drain the initial Join message
|
||||
<-ch.broadcast
|
||||
|
||||
go func() {
|
||||
for msg := range u.msg {
|
||||
if _, ok := msg.(*AnnounceMsg); ok {
|
||||
t.Errorf("Got unexpected `%T`", msg)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Call with an AnnounceMsg and all the other types
|
||||
// and assert we received only non-announce messages
|
||||
ch.HandleMsg(NewAnnounceMsg("Ignored"))
|
||||
// Assert we still get all other types of messages
|
||||
ch.HandleMsg(NewEmoteMsg("hello", u))
|
||||
ch.HandleMsg(NewSystemMsg("hello", u))
|
||||
ch.HandleMsg(NewPrivateMsg("hello", u, u))
|
||||
ch.HandleMsg(NewPublicMsg("hello", u))
|
||||
}
|
||||
|
||||
func TestChannelQuietToggleBroadcasts(t *testing.T) {
|
||||
u := NewUser("foo")
|
||||
u.Config = UserConfig{
|
||||
Quiet: true,
|
||||
}
|
||||
|
||||
ch := NewChannel()
|
||||
defer ch.Close()
|
||||
|
||||
err := ch.Join(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Drain the initial Join message
|
||||
<-ch.broadcast
|
||||
|
||||
u.ToggleQuietMode()
|
||||
|
||||
expectedMsg := NewAnnounceMsg("Ignored")
|
||||
ch.HandleMsg(expectedMsg)
|
||||
msg := <-u.msg
|
||||
if _, ok := msg.(*AnnounceMsg); !ok {
|
||||
t.Errorf("Got: `%T`; Expected: `%T`", msg, expectedMsg)
|
||||
}
|
||||
|
||||
u.ToggleQuietMode()
|
||||
|
||||
ch.HandleMsg(NewAnnounceMsg("Ignored"))
|
||||
ch.HandleMsg(NewSystemMsg("hello", u))
|
||||
msg = <-u.msg
|
||||
if _, ok := msg.(*AnnounceMsg); ok {
|
||||
t.Errorf("Got unexpected `%T`", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuietToggleDisplayState(t *testing.T) {
|
||||
var expected, actual []byte
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
|
||||
ch := NewChannel()
|
||||
go ch.Serve()
|
||||
defer ch.Close()
|
||||
|
||||
err := ch.Join(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Drain the initial Join message
|
||||
<-ch.broadcast
|
||||
|
||||
ch.Send(ParseInput("/quiet", u))
|
||||
u.ConsumeOne(s)
|
||||
expected = []byte("-> Quiet mode is toggled ON" + Newline)
|
||||
s.Read(&actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
|
||||
ch.Send(ParseInput("/quiet", u))
|
||||
u.ConsumeOne(s)
|
||||
expected = []byte("-> Quiet mode is toggled OFF" + Newline)
|
||||
|
||||
s.Read(&actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChannelNames(t *testing.T) {
|
||||
var expected, actual []byte
|
||||
|
||||
s := &MockScreen{}
|
||||
u := NewUser("foo")
|
||||
|
||||
ch := NewChannel()
|
||||
go ch.Serve()
|
||||
defer ch.Close()
|
||||
|
||||
err := ch.Join(u)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Drain the initial Join message
|
||||
<-ch.broadcast
|
||||
|
||||
ch.Send(ParseInput("/names", u))
|
||||
u.ConsumeOne(s)
|
||||
expected = []byte("-> 1 connected: foo" + Newline)
|
||||
s.Read(&actual)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Got: `%s`; Expected: `%s`", actual, expected)
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +196,24 @@ func InitCommands(c *Commands) {
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Prefix: "/quiet",
|
||||
Help: "Silence announcement-type messages (join, part, rename, etc).",
|
||||
Handler: func(channel *Channel, msg CommandMsg) error {
|
||||
u := msg.From()
|
||||
u.ToggleQuietMode()
|
||||
|
||||
var body string
|
||||
if u.Config.Quiet {
|
||||
body = "Quiet mode is toggled ON"
|
||||
} else {
|
||||
body = "Quiet mode is toggled OFF"
|
||||
}
|
||||
channel.Send(NewSystemMsg(body, u))
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
c.Add(Command{
|
||||
Op: true,
|
||||
Prefix: "/op",
|
||||
|
@ -59,6 +59,11 @@ func (u *User) SetName(name string) {
|
||||
u.SetColorIdx(rand.Int())
|
||||
}
|
||||
|
||||
// ToggleQuietMode will toggle whether or not quiet mode is enabled
|
||||
func (u *User) ToggleQuietMode() {
|
||||
u.Config.Quiet = !u.Config.Quiet
|
||||
}
|
||||
|
||||
// SetColorIdx will set the colorIdx to a specific value, primarily used for
|
||||
// testing.
|
||||
func (u *User) SetColorIdx(idx int) {
|
||||
@ -122,6 +127,7 @@ func (u *User) Send(m Message) error {
|
||||
type UserConfig struct {
|
||||
Highlight bool
|
||||
Bell bool
|
||||
Quiet bool
|
||||
Theme *Theme
|
||||
}
|
||||
|
||||
@ -132,6 +138,7 @@ func init() {
|
||||
DefaultUserConfig = &UserConfig{
|
||||
Highlight: true,
|
||||
Bell: false,
|
||||
Quiet: false,
|
||||
}
|
||||
|
||||
// TODO: Seed random?
|
||||
|
56
cmd.go
56
cmd.go
@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@ -14,6 +16,7 @@ import (
|
||||
"github.com/alexcesaro/log/golog"
|
||||
"github.com/jessevdk/go-flags"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
"github.com/shazow/ssh-chat/chat"
|
||||
"github.com/shazow/ssh-chat/sshd"
|
||||
@ -24,7 +27,7 @@ import _ "net/http/pprof"
|
||||
type Options struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose logging."`
|
||||
Identity string `short:"i" long:"identity" description:"Private key to identify server with." default:"~/.ssh/id_rsa"`
|
||||
Bind string `long:"bind" description:"Host and port to listen on." default:"0.0.0.0:22"`
|
||||
Bind string `long:"bind" description:"Host and port to listen on." default:"0.0.0.0:2022"`
|
||||
Admin []string `long:"admin" description:"Fingerprint of pubkey to mark as admin."`
|
||||
Whitelist string `long:"whitelist" description:"Optional file of pubkey fingerprints who are allowed to connect."`
|
||||
Motd string `long:"motd" description:"Optional Message of the Day file."`
|
||||
@ -80,21 +83,19 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
privateKey, err := ioutil.ReadFile(privateKeyPath)
|
||||
privateKey, err := readPrivateKey(privateKeyPath)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to load identity: %v", err)
|
||||
logger.Errorf("Couldn't read private key: %v", err)
|
||||
os.Exit(2)
|
||||
return
|
||||
}
|
||||
|
||||
signer, err := ssh.ParsePrivateKey(privateKey)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to parse key: %v", err)
|
||||
os.Exit(3)
|
||||
return
|
||||
}
|
||||
|
||||
auth := Auth{}
|
||||
auth := NewAuth()
|
||||
config := sshd.MakeAuth(auth)
|
||||
config.AddHostKey(signer)
|
||||
|
||||
@ -102,10 +103,11 @@ func main() {
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to listen on socket: %v", err)
|
||||
os.Exit(4)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
fmt.Printf("Listening for connections on %v\n", s.Addr().String())
|
||||
|
||||
host := NewHost(s)
|
||||
host.auth = &auth
|
||||
host.theme = &chat.Themes[0]
|
||||
@ -151,3 +153,43 @@ func main() {
|
||||
logger.Warningf("Interrupt signal detected, shutting down.")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// readPrivateKey attempts to read your private key and possibly decrypt it if it
|
||||
// requires a passphrase.
|
||||
// This function will prompt for a passphrase on STDIN if the environment variable (`IDENTITY_PASSPHRASE`),
|
||||
// is not set.
|
||||
func readPrivateKey(privateKeyPath string) ([]byte, error) {
|
||||
privateKey, err := ioutil.ReadFile(privateKeyPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load identity: %v", err)
|
||||
}
|
||||
|
||||
block, rest := pem.Decode(privateKey)
|
||||
if len(rest) > 0 {
|
||||
return nil, fmt.Errorf("extra data when decoding private key")
|
||||
}
|
||||
if !x509.IsEncryptedPEMBlock(block) {
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
passphrase := []byte(os.Getenv("IDENTITY_PASSPHRASE"))
|
||||
if len(passphrase) == 0 {
|
||||
fmt.Printf("Enter passphrase: ")
|
||||
passphrase, err = terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't read passphrase: %v", err)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
der, err := x509.DecryptPEMBlock(block, passphrase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypt failed: %v", err)
|
||||
}
|
||||
|
||||
privateKey = pem.EncodeToMemory(&pem.Block{
|
||||
Type: block.Type,
|
||||
Bytes: der,
|
||||
})
|
||||
|
||||
return privateKey, nil
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ package sshd
|
||||
config := MakeNoAuth()
|
||||
config.AddHostKey(signer)
|
||||
|
||||
s, err := ListenSSH("0.0.0.0:22", config)
|
||||
s, err := ListenSSH("0.0.0.0:2022", config)
|
||||
if err != nil {
|
||||
// Handle opening socket error
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user