Merge branch 'refactor' of github.com:shazow/ssh-chat into refactor

This commit is contained in:
Andrey Petrov 2015-01-06 21:43:08 -08:00
commit 7a39898b36
8 changed files with 227 additions and 11 deletions

View File

@ -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

View File

@ -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()

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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",

View File

@ -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
View File

@ -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
}

View File

@ -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
}