From 2a4a005b3cdb8ef970bb86f9ecd580feaef96bd9 Mon Sep 17 00:00:00 2001
From: empathetic-alligator <willipovell@gmail.com>
Date: Mon, 15 Dec 2014 23:55:46 -0500
Subject: [PATCH 01/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 00:02:58 -0500
Subject: [PATCH 02/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 00:04:38 -0500
Subject: [PATCH 03/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 00:07:16 -0500
Subject: [PATCH 04/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 01:23:43 -0500
Subject: [PATCH 05/10] 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 3c466dc88e40e598766631c80ece0acb57f29730 Mon Sep 17 00:00:00 2001
From: empathetic-alligator <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 19:59:52 -0500
Subject: [PATCH 06/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 20:47:39 -0500
Subject: [PATCH 07/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 21:15:45 -0500
Subject: [PATCH 08/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 21:18:10 -0500
Subject: [PATCH 09/10] 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 <willipovell@gmail.com>
Date: Tue, 16 Dec 2014 21:27:55 -0500
Subject: [PATCH 10/10] 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 {