From 7634d0b170df6bfda41f32e8c66bb75f6144bf69 Mon Sep 17 00:00:00 2001 From: Ken Piper Date: Mon, 15 Dec 2014 01:21:40 -0500 Subject: [PATCH 01/22] Make highlighting optional for broadcasts ...And beeping on broadcasts, too --- client.go | 10 +++++----- server.go | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/client.go b/client.go index 1168893..acc7cdf 100644 --- a/client.go +++ b/client.go @@ -233,7 +233,7 @@ func (c *Client) handleShell(channel ssh.Channel) { if c.IsSilenced() || len(msg) > 1000 { c.SysMsg("Message rejected.") } else { - c.Server.Broadcast(msg, nil) + c.Server.Broadcast(msg, nil, false) } case "/nick": if len(parts) == 2 { @@ -280,7 +280,7 @@ func (c *Client) handleShell(channel ssh.Channel) { client.SysMsg("Banned by %s.", c.ColoredName()) c.Server.Ban(fingerprint, nil) client.Conn.Close() - c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil) + c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil, true) } } case "/op": @@ -310,7 +310,7 @@ func (c *Client) handleShell(channel ssh.Channel) { } else { client.SysMsg("Kicked by %s.", c.ColoredName()) client.Conn.Close() - c.Server.Broadcast(fmt.Sprintf("* %s was kicked by %s", parts[1], c.ColoredName()), nil) + c.Server.Broadcast(fmt.Sprintf("* %s was kicked by %s", parts[1], c.ColoredName()), nil, true) } } case "/silence": @@ -347,7 +347,7 @@ func (c *Client) handleShell(channel ssh.Channel) { } // Shutdown after 5 seconds go func() { - c.Server.Broadcast(ColorString("31", msg), nil) + c.Server.Broadcast(ColorString("31", msg), nil, true) time.Sleep(time.Second * 5) c.Server.Stop() }() @@ -422,7 +422,7 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Message rejected.") continue } - c.Server.Broadcast(msg, c) + c.Server.Broadcast(msg, c, true) c.lastTX = time.Now() } diff --git a/server.go b/server.go index ef2cb82..b629688 100644 --- a/server.go +++ b/server.go @@ -91,11 +91,11 @@ func (s *Server) Len() int { // SysMsg broadcasts the given message to everyone func (s *Server) SysMsg(msg string, args ...interface{}) { - s.Broadcast(ContinuousFormat(systemMessageFormat, " * "+fmt.Sprintf(msg, args...)), nil) + s.Broadcast(ContinuousFormat(systemMessageFormat, " * "+fmt.Sprintf(msg, args...)), nil, false) } // Broadcast broadcasts the given message to everyone except for the given client -func (s *Server) Broadcast(msg string, except *Client) { +func (s *Server) Broadcast(msg string, except *Client, canHighlight bool) { logger.Debugf("Broadcast to %d: %s", s.Len(), msg) s.history.Add(msg) @@ -104,7 +104,7 @@ func (s *Server) Broadcast(msg string, except *Client) { continue } - if strings.Contains(msg, client.Name) { + if strings.Contains(msg, client.Name) && canHighlight { // Turn message red if client's name is mentioned, and send BEL if they have enabled beeping tmpMsg := strings.Split(msg, Reset) if client.beepMe { @@ -150,8 +150,8 @@ func (s *Server) MotdBroadcast(client *Client) { if s.motd == "" { return } - s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client) - s.Broadcast(s.motd, client) + s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client, false) + s.Broadcast(s.motd, client, false) } // Add adds the client to the list of clients @@ -174,7 +174,7 @@ func (s *Server) Add(client *Client) { num := len(s.clients) s.Unlock() - s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * %s joined. (Total connected: %d)", client.ColoredName(), num)), client) + s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * %s joined. (Total connected: %d)", client.ColoredName(), num)), client, false) } // Remove removes the given client from the list of clients From 2b90eab1d5a15f965764b07c0179ba02b14729e9 Mon Sep 17 00:00:00 2001 From: Song Gao Date: Mon, 15 Dec 2014 10:39:27 -0600 Subject: [PATCH 02/22] Add KeyboardInteractiveCallback so that even client do now have publickey, username can still be passed in --- server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server.go b/server.go index bd4230e..3465170 100644 --- a/server.go +++ b/server.go @@ -77,6 +77,9 @@ func NewServer(privateKey []byte) (*Server, error) { perm := &ssh.Permissions{Extensions: map[string]string{"fingerprint": fingerprint}} return perm, nil }, + KeyboardInteractiveCallback: func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) { + return nil, nil + }, } config.AddHostKey(signer) From 64e0dbc5c47a0924628c2dfd2e9861b26c89c455 Mon Sep 17 00:00:00 2001 From: Ken Piper Date: Mon, 15 Dec 2014 20:25:10 -0500 Subject: [PATCH 03/22] Revert 7634d0b170df6bfda41f32e8c66bb75f6144bf69 --- client.go | 8 ++++---- server.go | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client.go b/client.go index 438b151..f3d886b 100644 --- a/client.go +++ b/client.go @@ -293,7 +293,7 @@ func (c *Client) handleShell(channel ssh.Channel) { client.SysMsg("Banned by %s.", c.ColoredName()) c.Server.Ban(fingerprint, nil) client.Conn.Close() - c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil, true) + c.Server.Broadcast(fmt.Sprintf("* %s was banned by %s", parts[1], c.ColoredName()), nil) } } case "/op": @@ -323,7 +323,7 @@ func (c *Client) handleShell(channel ssh.Channel) { } else { client.SysMsg("Kicked by %s.", c.ColoredName()) client.Conn.Close() - c.Server.Broadcast(fmt.Sprintf("* %s was kicked by %s", parts[1], c.ColoredName()), nil, true) + c.Server.Broadcast(fmt.Sprintf("* %s was kicked by %s", parts[1], c.ColoredName()), nil) } } case "/silence": @@ -360,7 +360,7 @@ func (c *Client) handleShell(channel ssh.Channel) { } // Shutdown after 5 seconds go func() { - c.Server.Broadcast(ColorString("31", msg), nil, true) + c.Server.Broadcast(ColorString("31", msg), nil) time.Sleep(time.Second * 5) c.Server.Stop() }() @@ -435,7 +435,7 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Message rejected.") continue } - c.Server.Broadcast(msg, c, true) + c.Server.Broadcast(msg, c) c.lastTX = time.Now() } diff --git a/server.go b/server.go index 2f39941..bd4230e 100644 --- a/server.go +++ b/server.go @@ -92,11 +92,11 @@ func (s *Server) Len() int { // SysMsg broadcasts the given message to everyone func (s *Server) SysMsg(msg string, args ...interface{}) { - s.Broadcast(ContinuousFormat(systemMessageFormat, " * "+fmt.Sprintf(msg, args...)), nil, false) + s.Broadcast(ContinuousFormat(systemMessageFormat, " * "+fmt.Sprintf(msg, args...)), nil) } // Broadcast broadcasts the given message to everyone except for the given client -func (s *Server) Broadcast(msg string, except *Client, canHighlight bool) { +func (s *Server) Broadcast(msg string, except *Client) { logger.Debugf("Broadcast to %d: %s", s.Len(), msg) s.history.Add(msg) @@ -105,7 +105,7 @@ func (s *Server) Broadcast(msg string, except *Client, canHighlight bool) { continue } - if strings.Contains(msg, client.Name) && canHighlight { + if strings.Contains(msg, client.Name) { // Turn message red if client's name is mentioned, and send BEL if they have enabled beeping personalMsg := strings.Replace(msg, client.Name, highlightFormat+client.Name+Reset, -1) if client.beepMe { @@ -151,8 +151,8 @@ func (s *Server) MotdBroadcast(client *Client) { if s.motd == "" { return } - s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client, false) - s.Broadcast(s.motd, client, false) + s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client) + s.Broadcast(s.motd, client) } // Add adds the client to the list of clients From 1afe84925de95b4d3668dc78fd689e17f88b4ac0 Mon Sep 17 00:00:00 2001 From: Ken Piper Date: Mon, 15 Dec 2014 20:31:13 -0500 Subject: [PATCH 04/22] Make /whois ignore any trailing spaces/text --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index f3d886b..1a6a795 100644 --- a/client.go +++ b/client.go @@ -255,7 +255,7 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Missing $NAME from: /nick $NAME") } case "/whois": - if len(parts) == 2 { + if len(parts) >= 2 { client := c.Server.Who(parts[1]) if client != nil { version := reStripText.ReplaceAllString(string(client.Conn.ClientVersion()), "") From 2a4a005b3cdb8ef970bb86f9ecd580feaef96bd9 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Mon, 15 Dec 2014 23:55:46 -0500 Subject: [PATCH 05/22] Can now whitelist users by github pubkeys. --- client.go | 8 +++++-- server.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index f3d886b..da6223a 100644 --- a/client.go +++ b/client.go @@ -415,8 +415,12 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Missing $FINGERPRINT from: /whitelist $FINGERPRINT") } else { fingerprint := parts[1] - c.Server.Whitelist(fingerprint) - c.SysMsg("Added %s to the whitelist", fingerprint) + err = c.Server.Whitelist(fingerprint) + if err != nil { + c.SysMsg("Error adding to whitelist: %s", err) + } else { + c.SysMsg("Added %s to the whitelist", fingerprint) + } } default: diff --git a/server.go b/server.go index bd4230e..ea86f09 100644 --- a/server.go +++ b/server.go @@ -9,6 +9,10 @@ import ( "sync" "syscall" "time" + "net/http" + "io/ioutil" + "encoding/base64" + "errors" "golang.org/x/crypto/ssh" ) @@ -260,11 +264,65 @@ func (s *Server) Op(fingerprint string) { } // Whitelist adds the given fingerprint to the whitelist -func (s *Server) Whitelist(fingerprint string) { - logger.Infof("Adding whitelist: %s", fingerprint) - s.Lock() - s.whitelist[fingerprint] = struct{}{} - s.Unlock() +func (s *Server) Whitelist(fingerprint string) error { + if strings.HasPrefix(fingerprint, "github.com/") { + logger.Infof("Adding github account %s to whitelist", fingerprint) + + keys, err := getGithubKey(fingerprint) + if err != nil { + return err + } + if len(keys) == 0 { + return errors.New(fmt.Sprintf("No github user %s", fingerprint)) + } + for _, key := range keys { + fingerprint = Fingerprint(key) + logger.Infof("Adding whitelist: %s", fingerprint) + s.Lock() + s.whitelist[fingerprint] = struct{}{} + s.Unlock() + } + } else { + logger.Infof("Adding whitelist: %s", fingerprint) + s.Lock() + s.whitelist[fingerprint] = struct{}{} + s.Unlock() + } + return nil +} + +var r *regexp.Regexp = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +func getGithubKey(url string) ([]ssh.PublicKey, error) { + resp, err := http.Get("http://" + url + ".keys") + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + body_str := string(body) + keys := r.FindAllStringSubmatch(body_str, -1) + pubs := make([]ssh.PublicKey, 0, 3) + for _, key := range keys { + if(len(key) < 2) { + continue + } + + body_decoded, err := base64.StdEncoding.DecodeString(key[1]) + if err != nil { + return nil, err + } + + pub, err := ssh.ParsePublicKey(body_decoded) + if err != nil { + return nil, err + } + + pubs = append(pubs, pub) + } + return pubs, nil } // Uptime returns the time since the server was started From da7ee40d95fb5f989849be5f45e146a7d6ef9d0b Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 00:02:58 -0500 Subject: [PATCH 06/22] Fixed issues reported by golint. --- server.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index ea86f09..95a5228 100644 --- a/server.go +++ b/server.go @@ -12,7 +12,6 @@ import ( "net/http" "io/ioutil" "encoding/base64" - "errors" "golang.org/x/crypto/ssh" ) @@ -273,7 +272,7 @@ func (s *Server) Whitelist(fingerprint string) error { return err } if len(keys) == 0 { - return errors.New(fmt.Sprintf("No github user %s", fingerprint)) + return fmt.Errorf("No github user %s", fingerprint) } for _, key := range keys { fingerprint = Fingerprint(key) @@ -291,7 +290,7 @@ func (s *Server) Whitelist(fingerprint string) error { return nil } -var r *regexp.Regexp = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +var r = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) func getGithubKey(url string) ([]ssh.PublicKey, error) { resp, err := http.Get("http://" + url + ".keys") if err != nil { @@ -302,20 +301,20 @@ func getGithubKey(url string) ([]ssh.PublicKey, error) { if err != nil { return nil, err } - body_str := string(body) - keys := r.FindAllStringSubmatch(body_str, -1) + bodyStr := string(body) + keys := r.FindAllStringSubmatch(bodyStr, -1) pubs := make([]ssh.PublicKey, 0, 3) for _, key := range keys { if(len(key) < 2) { continue } - body_decoded, err := base64.StdEncoding.DecodeString(key[1]) + bodyDecoded, err := base64.StdEncoding.DecodeString(key[1]) if err != nil { return nil, err } - pub, err := ssh.ParsePublicKey(body_decoded) + pub, err := ssh.ParsePublicKey(bodyDecoded) if err != nil { return nil, err } From 3d7ff07587713946479b33566d58d1ea1e1ccc74 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 00:04:38 -0500 Subject: [PATCH 07/22] Updated help text for /whitelist --- client.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index da6223a..107d81d 100644 --- a/client.go +++ b/client.go @@ -32,13 +32,14 @@ const ( // OpHelpText is the additional text returned by /help if the client is an Op OpHelpText string = systemMessageFormat + `-> Available operator commands: - /ban $NAME - Banish a user from the chat - /kick $NAME - Kick em' out. - /op $NAME - Promote a user to server operator. - /silence $NAME - Revoke a user's ability to speak. - /shutdown $MESSAGE - Broadcast message and shutdown server. - /motd $MESSAGE - Set message shown whenever somebody joins. - /whitelist $FINGERPRINT - Add fingerprint to whitelist, prevent anyone else from joining.` + Reset + /ban $NAME - Banish a user from the chat + /kick $NAME - Kick em' out. + /op $NAME - Promote a user to server operator. + /silence $NAME - Revoke a user's ability to speak. + /shutdown $MESSAGE - Broadcast message and shutdown server. + /motd $MESSAGE - Set message shown whenever somebody joins. + /whitelist $FINGERPRINT - Add fingerprint to whitelist, prevent anyone else from joining. + /whitelist github.com/$USER - Add github user's pubkeys to whitelist.` + Reset // AboutText is the text returned by /about AboutText string = systemMessageFormat + `-> ssh-chat is made by @shazow. From 932410d0575c73888032ffd815dbe704bef49be4 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 00:07:16 -0500 Subject: [PATCH 08/22] Added comments and better variable names in getGithubPubKeys. --- server.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 95a5228..d22db99 100644 --- a/server.go +++ b/server.go @@ -267,7 +267,7 @@ func (s *Server) Whitelist(fingerprint string) error { if strings.HasPrefix(fingerprint, "github.com/") { logger.Infof("Adding github account %s to whitelist", fingerprint) - keys, err := getGithubKey(fingerprint) + keys, err := getGithubPubKeys(fingerprint) if err != nil { return err } @@ -290,8 +290,9 @@ func (s *Server) Whitelist(fingerprint string) error { return nil } -var r = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) -func getGithubKey(url string) ([]ssh.PublicKey, error) { +var pubKeyRegex = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +// Returns an array of public keys for the given github user URL +func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { resp, err := http.Get("http://" + url + ".keys") if err != nil { return nil, err @@ -302,7 +303,7 @@ func getGithubKey(url string) ([]ssh.PublicKey, error) { return nil, err } bodyStr := string(body) - keys := r.FindAllStringSubmatch(bodyStr, -1) + keys := pubKeyRegex.FindAllStringSubmatch(bodyStr, -1) pubs := make([]ssh.PublicKey, 0, 3) for _, key := range keys { if(len(key) < 2) { From 8329c8d7fd1447e09d494e859f7b5eeb790208f0 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 01:23:43 -0500 Subject: [PATCH 09/22] Made whitelisting user async. --- client.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index 107d81d..b780aa9 100644 --- a/client.go +++ b/client.go @@ -416,12 +416,14 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Missing $FINGERPRINT from: /whitelist $FINGERPRINT") } else { fingerprint := parts[1] - err = c.Server.Whitelist(fingerprint) - if err != nil { - c.SysMsg("Error adding to whitelist: %s", err) - } else { - c.SysMsg("Added %s to the whitelist", fingerprint) - } + go func() { + err = c.Server.Whitelist(fingerprint) + if err != nil { + c.SysMsg("Error adding to whitelist: %s", err) + } else { + c.SysMsg("Added %s to the whitelist", fingerprint) + } + }() } default: From b90017bfe6f96252e9db96bb972d27f2c7246a66 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 14:29:45 -0800 Subject: [PATCH 10/22] Fix crash bug. --- .gitignore | 1 + client.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1bec925..6207059 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ host_key host_key.pub ssh-chat +*.log diff --git a/client.go b/client.go index 1a6a795..3e0290d 100644 --- a/client.go +++ b/client.go @@ -94,7 +94,7 @@ func (c *Client) ColoredName() string { // SysMsg sends a message in continuous format over the message channel func (c *Client) SysMsg(msg string, args ...interface{}) { - c.Msg <- ContinuousFormat(systemMessageFormat, "-> "+fmt.Sprintf(msg, args...)) + c.Send(ContinuousFormat(systemMessageFormat, "-> "+fmt.Sprintf(msg, args...))) } // Write writes the given message From 3c466dc88e40e598766631c80ece0acb57f29730 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 19:59:52 -0500 Subject: [PATCH 11/22] Added timeout to github pubkey request. --- server.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index c425b80..702f7b5 100644 --- a/server.go +++ b/server.go @@ -296,7 +296,12 @@ func (s *Server) Whitelist(fingerprint string) error { var pubKeyRegex = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) // Returns an array of public keys for the given github user URL func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { - resp, err := http.Get("http://" + url + ".keys") + timeout := time.Duration(10 * time.Second) + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Get("http://" + url + ".keys") + if err != nil { return nil, err } From a1455a8ebaec501e670c8ef2b019d2fcbfbe679c Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 20:47:39 -0500 Subject: [PATCH 12/22] Removed regex, added timeout. --- server.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/server.go b/server.go index 702f7b5..360c52e 100644 --- a/server.go +++ b/server.go @@ -293,32 +293,35 @@ func (s *Server) Whitelist(fingerprint string) error { return nil } -var pubKeyRegex = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +// Client for getting github pub keys +var timeout = time.Duration(10 * time.Second) +var client = http.Client{ + Timeout: timeout, +} // Returns an array of public keys for the given github user URL func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { - timeout := time.Duration(10 * time.Second) - client := http.Client{ - Timeout: timeout, - } resp, err := client.Get("http://" + url + ".keys") - if err != nil { return nil, err } defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } + bodyStr := string(body) - keys := pubKeyRegex.FindAllStringSubmatch(bodyStr, -1) - pubs := make([]ssh.PublicKey, 0, 3) - for _, key := range keys { - if(len(key) < 2) { + pubs := []ssh.PublicKey{} + for _, key := range strings.SplitN(bodyStr, "\n", -1) { + splitKey := strings.SplitN(key, " ", -1) + + // In case of malformated key + if len(splitKey) < 2 { continue } - bodyDecoded, err := base64.StdEncoding.DecodeString(key[1]) + bodyDecoded, err := base64.StdEncoding.DecodeString(splitKey[1]) if err != nil { return nil, err } From 912175e65a0ec8c033eab47b719048d8980034fc Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 21:15:45 -0500 Subject: [PATCH 13/22] Split up whitelist func, made identity url get safer. --- server.go | 57 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/server.go b/server.go index 360c52e..7951fa3 100644 --- a/server.go +++ b/server.go @@ -268,28 +268,35 @@ func (s *Server) Op(fingerprint string) { // Whitelist adds the given fingerprint to the whitelist func (s *Server) Whitelist(fingerprint string) error { if strings.HasPrefix(fingerprint, "github.com/") { - logger.Infof("Adding github account %s to whitelist", fingerprint) - - keys, err := getGithubPubKeys(fingerprint) - if err != nil { - return err - } - if len(keys) == 0 { - return fmt.Errorf("No github user %s", fingerprint) - } - for _, key := range keys { - fingerprint = Fingerprint(key) - logger.Infof("Adding whitelist: %s", fingerprint) - s.Lock() - s.whitelist[fingerprint] = struct{}{} - s.Unlock() - } + return s.whitelistIdentityUrl(fingerprint) } else { - logger.Infof("Adding whitelist: %s", fingerprint) - s.Lock() - s.whitelist[fingerprint] = struct{}{} - s.Unlock() + return s.whitelistFingerprint(fingerprint) } +} + +func (s *Server) whitelistIdentityUrl(user string) error { + logger.Infof("Adding github account %s to whitelist", user) + + user = strings.Replace(user, "github.com/", "", -1) + keys, err := getGithubPubKeys(user) + if err != nil { + return err + } + if len(keys) == 0 { + return fmt.Errorf("No keys for github user %s", user) + } + for _, key := range keys { + fingerprint := Fingerprint(key) + s.whitelistFingerprint(fingerprint) + } + return nil +} + +func (s *Server) whitelistFingerprint(fingerprint string) error { + logger.Infof("Adding whitelist: %s", fingerprint) + s.Lock() + s.whitelist[fingerprint] = struct{}{} + s.Unlock() return nil } @@ -299,8 +306,8 @@ var client = http.Client{ Timeout: timeout, } // Returns an array of public keys for the given github user URL -func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { - resp, err := client.Get("http://" + url + ".keys") +func getGithubPubKeys(user string) ([]ssh.PublicKey, error) { + resp, err := client.Get("http://github.com/" + user + ".keys") if err != nil { return nil, err } @@ -312,6 +319,12 @@ func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { } bodyStr := string(body) + + // More informative error than that from base64 DecodeString + if bodyStr == "Not Found" { + return nil, fmt.Errorf("No github user %s found", user) + } + pubs := []ssh.PublicKey{} for _, key := range strings.SplitN(bodyStr, "\n", -1) { splitKey := strings.SplitN(key, " ", -1) From 580ad79a22b818c45b1859d6f636e94c521f7548 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 21:18:10 -0500 Subject: [PATCH 14/22] Fixed issues found by golint. --- server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 7951fa3..2ef62b8 100644 --- a/server.go +++ b/server.go @@ -268,13 +268,13 @@ func (s *Server) Op(fingerprint string) { // Whitelist adds the given fingerprint to the whitelist func (s *Server) Whitelist(fingerprint string) error { if strings.HasPrefix(fingerprint, "github.com/") { - return s.whitelistIdentityUrl(fingerprint) - } else { - return s.whitelistFingerprint(fingerprint) + return s.whitelistIdentityURL(fingerprint) } + + return s.whitelistFingerprint(fingerprint) } -func (s *Server) whitelistIdentityUrl(user string) error { +func (s *Server) whitelistIdentityURL(user string) error { logger.Infof("Adding github account %s to whitelist", user) user = strings.Replace(user, "github.com/", "", -1) From 68e82ff1155d54e6b752f3394a7e56567f4262d7 Mon Sep 17 00:00:00 2001 From: empathetic-alligator Date: Tue, 16 Dec 2014 21:27:55 -0500 Subject: [PATCH 15/22] Using bufio instead of ioutil. --- server.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/server.go b/server.go index 2ef62b8..74d3f3c 100644 --- a/server.go +++ b/server.go @@ -10,7 +10,7 @@ import ( "syscall" "time" "net/http" - "io/ioutil" + "bufio" "encoding/base64" "golang.org/x/crypto/ssh" @@ -313,21 +313,15 @@ func getGithubPubKeys(user string) ([]ssh.PublicKey, error) { } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - bodyStr := string(body) - - // More informative error than that from base64 DecodeString - if bodyStr == "Not Found" { - return nil, fmt.Errorf("No github user %s found", user) - } - pubs := []ssh.PublicKey{} - for _, key := range strings.SplitN(bodyStr, "\n", -1) { - splitKey := strings.SplitN(key, " ", -1) + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + text := scanner.Text() + if text == "Not Found" { + continue + } + + splitKey := strings.SplitN(text, " ", -1) // In case of malformated key if len(splitKey) < 2 { From 5965172183921031f2e1c2217bb550dc22c1769e Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 19:20:45 -0800 Subject: [PATCH 16/22] Nil pointer crash fix. --- client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client.go b/client.go index e68d7f5..1ec039b 100644 --- a/client.go +++ b/client.go @@ -171,6 +171,9 @@ func (c *Client) Rename(name string) { // Fingerprint returns the fingerprint func (c *Client) Fingerprint() string { + if c.Conn.Permissions == nil { + return "" + } return c.Conn.Permissions.Extensions["fingerprint"] } From f957079489f3c7f279bff24d44fca44aa10879d0 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 19:22:00 -0800 Subject: [PATCH 17/22] Fix literal referenc. --- server.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index 74d3f3c..205d848 100644 --- a/server.go +++ b/server.go @@ -1,17 +1,17 @@ package main import ( + "bufio" "crypto/md5" + "encoding/base64" "fmt" "net" + "net/http" "regexp" "strings" "sync" "syscall" "time" - "net/http" - "bufio" - "encoding/base64" "golang.org/x/crypto/ssh" ) @@ -301,10 +301,10 @@ func (s *Server) whitelistFingerprint(fingerprint string) error { } // Client for getting github pub keys -var timeout = time.Duration(10 * time.Second) var client = http.Client{ - Timeout: timeout, + Timeout: time.Duration(10 * time.Second), } + // Returns an array of public keys for the given github user URL func getGithubPubKeys(user string) ([]ssh.PublicKey, error) { resp, err := client.Get("http://github.com/" + user + ".keys") From bb0c8e9e4926cb62022a04d6ef4a648e37e4c546 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 19:39:32 -0800 Subject: [PATCH 18/22] Disable no keypair login. --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 205d848..7849541 100644 --- a/server.go +++ b/server.go @@ -81,7 +81,7 @@ func NewServer(privateKey []byte) (*Server, error) { return perm, nil }, KeyboardInteractiveCallback: func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) { - return nil, nil + return nil, fmt.Errorf("Must have an SSH keypair to connect.") }, } config.AddHostKey(signer) From 8bba2e391797ad21f1dc4ae3751f6ab9a8744791 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 20:15:37 -0800 Subject: [PATCH 19/22] Fix motd, invalid fingerprint, no auth whitelist. --- client.go | 23 +++++++++++------------ motd.txt | 2 +- server.go | 11 ++++++++++- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/client.go b/client.go index 1ec039b..35b6ab3 100644 --- a/client.go +++ b/client.go @@ -14,10 +14,10 @@ const ( MsgBuffer int = 50 // MaxMsgLength is the maximum length of a message - MaxMsgLength int = 512 + MaxMsgLength int = 1024 // HelpText is the text returned by /help - HelpText string = systemMessageFormat + `-> Available commands: + HelpText string = `Available commands: /about - About this chat. /exit - Exit the chat. /help - Show this help text. @@ -28,10 +28,10 @@ const ( /whois $NAME - Display information about another connected user. /msg $NAME $MESSAGE - Sends a private message to a user. /motd - Prints the Message of the Day. - /theme [color|mono] - Set client theme.` + Reset + /theme [color|mono] - Set client theme.` // OpHelpText is the additional text returned by /help if the client is an Op - OpHelpText string = systemMessageFormat + `-> Available operator commands: + OpHelpText string = `Available operator commands: /ban $NAME - Banish a user from the chat /kick $NAME - Kick em' out. /op $NAME - Promote a user to server operator. @@ -39,18 +39,17 @@ const ( /shutdown $MESSAGE - Broadcast message and shutdown server. /motd $MESSAGE - Set message shown whenever somebody joins. /whitelist $FINGERPRINT - Add fingerprint to whitelist, prevent anyone else from joining. - /whitelist github.com/$USER - Add github user's pubkeys to whitelist.` + Reset + /whitelist github.com/$USER - Add github user's pubkeys to whitelist.` // AboutText is the text returned by /about - AboutText string = systemMessageFormat + `-> ssh-chat is made by @shazow. + AboutText string = `ssh-chat is made by @shazow. It is a custom ssh server built in Go to serve a chat experience instead of a shell. Source: https://github.com/shazow/ssh-chat - For more, visit shazow.net or follow at twitter.com/shazow -` + Reset + For more, visit shazow.net or follow at twitter.com/shazow` // RequiredWait is the time a client is required to wait between messages RequiredWait time.Duration = time.Second / 2 @@ -222,14 +221,14 @@ func (c *Client) handleShell(channel ssh.Channel) { case "/exit": channel.Close() case "/help": - c.WriteLines(strings.Split(HelpText, "\n")) + c.SysMsg(strings.Replace(HelpText, "\n", "\r\n", -1)) if c.Server.IsOp(c) { - c.WriteLines(strings.Split(OpHelpText, "\n")) + c.SysMsg(strings.Replace(OpHelpText, "\n", "\r\n", -1)) } case "/about": - c.WriteLines(strings.Split(AboutText, "\n")) + c.SysMsg(strings.Replace(AboutText, "\n", "\r\n", -1)) case "/uptime": - c.Write(c.Server.Uptime()) + c.SysMsg(c.Server.Uptime()) case "/beep": c.beepMe = !c.beepMe if c.beepMe { diff --git a/motd.txt b/motd.txt index ac8395e..da0f972 100644 --- a/motd.txt +++ b/motd.txt @@ -1 +1 @@ -Welcome to chat.shazow.net, enter /help for more.  \ No newline at end of file +Welcome to chat.shazow.net, enter /help for more.  diff --git a/server.go b/server.go index 7849541..beacde7 100644 --- a/server.go +++ b/server.go @@ -81,7 +81,13 @@ func NewServer(privateKey []byte) (*Server, error) { return perm, nil }, KeyboardInteractiveCallback: func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) { - return nil, fmt.Errorf("Must have an SSH keypair to connect.") + if server.IsBanned("") { + return nil, fmt.Errorf("Interactive login disabled.") + } + if !server.IsWhitelisted("") { + return nil, fmt.Errorf("Not Whitelisted.") + } + return nil, nil }, } config.AddHostKey(signer) @@ -267,6 +273,9 @@ func (s *Server) Op(fingerprint string) { // Whitelist adds the given fingerprint to the whitelist func (s *Server) Whitelist(fingerprint string) error { + if fingerprint == "" { + return fmt.Errorf("Invalid fingerprint.") + } if strings.HasPrefix(fingerprint, "github.com/") { return s.whitelistIdentityURL(fingerprint) } From 3025e59935d5457f033c316faa957370d790cd43 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 22:12:14 -0800 Subject: [PATCH 20/22] Fixing another crash bug? --- client.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 35b6ab3..39671e6 100644 --- a/client.go +++ b/client.go @@ -3,6 +3,7 @@ package main import ( "fmt" "strings" + "sync" "time" "golang.org/x/crypto/ssh" @@ -71,6 +72,8 @@ type Client struct { lastTX time.Time beepMe bool colorMe bool + closed bool + sync.Mutex } // NewClient constructs a new client @@ -114,7 +117,7 @@ func (c *Client) WriteLines(msg []string) { // Send sends the given message func (c *Client) Send(msg string) { - if len(msg) > MaxMsgLength { + if len(msg) > MaxMsgLength || c.closed { return } select { @@ -193,8 +196,9 @@ func (c *Client) handleShell(channel ssh.Channel) { go func() { // Block until done, then remove. c.Conn.Wait() - close(c.Msg) + c.closed = true c.Server.Remove(c) + close(c.Msg) }() go func() { From 99c2cf1756a8002745ad9a1eeaa58694203ddeb3 Mon Sep 17 00:00:00 2001 From: Andrey Petrov Date: Tue, 16 Dec 2014 22:35:04 -0800 Subject: [PATCH 21/22] Fixing race conditions. --- client.go | 2 +- server.go | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client.go b/client.go index 39671e6..2c1e0d8 100644 --- a/client.go +++ b/client.go @@ -73,7 +73,7 @@ type Client struct { beepMe bool colorMe bool closed bool - sync.Mutex + sync.RWMutex } // NewClient constructs a new client diff --git a/server.go b/server.go index beacde7..2591c50 100644 --- a/server.go +++ b/server.go @@ -44,7 +44,7 @@ type Server struct { admins map[string]struct{} // fingerprint lookup bannedPK map[string]*time.Time // fingerprint lookup started time.Time - sync.Mutex + sync.RWMutex } // NewServer constructs a new server @@ -112,6 +112,9 @@ func (s *Server) Broadcast(msg string, except *Client) { logger.Debugf("Broadcast to %d: %s", s.Len(), msg) s.history.Add(msg) + s.RLock() + defer s.RUnlock() + for _, client := range s.clients { if except != nil && client == except { continue @@ -145,9 +148,7 @@ func (s *Server) Privmsg(nick, message string, sender *Client) error { // SetMotd sets the Message of the Day (MOTD) func (s *Server) SetMotd(motd string) { - s.Lock() s.motd = motd - s.Unlock() } // MotdUnicast sends the MOTD as a SysMsg @@ -248,6 +249,9 @@ func (s *Server) Rename(client *Client, newName string) { func (s *Server) List(prefix *string) []string { r := []string{} + s.RLock() + defer s.RUnlock() + for name := range s.clients { if prefix != nil && !strings.HasPrefix(name, *prefix) { continue @@ -494,9 +498,11 @@ func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine s // Stop stops the server func (s *Server) Stop() { + s.Lock() for _, client := range s.clients { client.Conn.Close() } + s.Unlock() close(s.done) } From a160bc9bac48cbd009e6a9c98c48a1e14221f3f2 Mon Sep 17 00:00:00 2001 From: Murilo Santana Date: Wed, 17 Dec 2014 09:26:29 -0200 Subject: [PATCH 22/22] moving Lock() call --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 2591c50..0ffa160 100644 --- a/server.go +++ b/server.go @@ -222,12 +222,12 @@ func (s *Server) proposeName(name string) (string, error) { // Rename renames the given client (user) func (s *Server) Rename(client *Client, newName string) { - s.Lock() var oldName string if strings.ToLower(newName) == strings.ToLower(client.Name) { oldName = client.Name client.Rename(newName) } else { + s.Lock() newName, err := s.proposeName(newName) if err != nil { client.SysMsg("%s", err)