Merge pull request #51 from peterhellberg/lint-fix

Fix all lint issues (mainly ALL_CAPS and comments)
This commit is contained in:
Andrey Petrov 2014-12-14 17:14:07 -08:00
commit b81324a5b4
5 changed files with 156 additions and 79 deletions

View File

@ -9,10 +9,15 @@ import (
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
const MSG_BUFFER int = 50 const (
const MAX_MSG_LENGTH int = 512 // MsgBuffer is the length of the message buffer
MsgBuffer int = 50
const HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available commands: // MaxMsgLength is the maximum length of a message
MaxMsgLength int = 512
// HelpText is the text returned by /help
HelpText string = systemMessageFormat + `-> Available commands:
/about - About this chat. /about - About this chat.
/exit - Exit the chat. /exit - Exit the chat.
/help - Show this help text. /help - Show this help text.
@ -24,9 +29,10 @@ const HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available commands:
/msg $NAME $MESSAGE - Sends a private message to a user. /msg $NAME $MESSAGE - Sends a private message to a user.
/motd - Prints the Message of the Day /motd - Prints the Message of the Day
/theme [color|mono] - Set client theme /theme [color|mono] - Set client theme
` + RESET ` + Reset
const OP_HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available operator commands: // 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 /ban $NAME - Banish a user from the chat
/kick $NAME - Kick em' out. /kick $NAME - Kick em' out.
/op $NAME - Promote a user to server operator. /op $NAME - Promote a user to server operator.
@ -34,9 +40,10 @@ const OP_HELP_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> Available operator comma
/shutdown $MESSAGE - Broadcast message and shutdown server /shutdown $MESSAGE - Broadcast message and shutdown server
/motd $MESSAGE - Sets the Message of the Day /motd $MESSAGE - Sets the Message of the Day
/whitelist $FINGERPRINT - Adds pubkey fingerprint to the connection whitelist /whitelist $FINGERPRINT - Adds pubkey fingerprint to the connection whitelist
` + RESET ` + Reset
const ABOUT_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> ssh-chat is made by @shazow. // AboutText is the text returned by /about
AboutText string = systemMessageFormat + `-> ssh-chat is made by @shazow.
It is a custom ssh server built in Go to serve a chat experience It is a custom ssh server built in Go to serve a chat experience
instead of a shell. instead of a shell.
@ -44,10 +51,13 @@ const ABOUT_TEXT string = SYSTEM_MESSAGE_FORMAT + `-> ssh-chat is made by @shazo
Source: https://github.com/shazow/ssh-chat Source: https://github.com/shazow/ssh-chat
For more, visit shazow.net or follow at twitter.com/shazow For more, visit shazow.net or follow at twitter.com/shazow
` + RESET ` + Reset
const REQUIRED_WAIT time.Duration = time.Second / 2 // RequiredWait is the time a client is required to wait between messages
RequiredWait time.Duration = time.Second / 2
)
// Client holds all the fields used by the client
type Client struct { type Client struct {
Server *Server Server *Server
Conn *ssh.ServerConn Conn *ssh.ServerConn
@ -65,42 +75,48 @@ type Client struct {
colorMe bool colorMe bool
} }
// NewClient constructs a new client
func NewClient(server *Server, conn *ssh.ServerConn) *Client { func NewClient(server *Server, conn *ssh.ServerConn) *Client {
return &Client{ return &Client{
Server: server, Server: server,
Conn: conn, Conn: conn,
Name: conn.User(), Name: conn.User(),
Color: RandomColor256(), Color: RandomColor256(),
Msg: make(chan string, MSG_BUFFER), Msg: make(chan string, MsgBuffer),
ready: make(chan struct{}, 1), ready: make(chan struct{}, 1),
lastTX: time.Now(), lastTX: time.Now(),
colorMe: true, colorMe: true,
} }
} }
// ColoredName returns the client name in its color
func (c *Client) ColoredName() string { func (c *Client) ColoredName() string {
return ColorString(c.Color, c.Name) return ColorString(c.Color, c.Name)
} }
// SysMsg sends a message in continuous format over the message channel
func (c *Client) SysMsg(msg string, args ...interface{}) { func (c *Client) SysMsg(msg string, args ...interface{}) {
c.Msg <- ContinuousFormat(SYSTEM_MESSAGE_FORMAT, "-> "+fmt.Sprintf(msg, args...)) c.Msg <- ContinuousFormat(systemMessageFormat, "-> "+fmt.Sprintf(msg, args...))
} }
// Write writes the given message
func (c *Client) Write(msg string) { func (c *Client) Write(msg string) {
if(!c.colorMe) { if !c.colorMe {
msg = DeColorString(msg) msg = DeColorString(msg)
} }
c.term.Write([]byte(msg + "\r\n")) c.term.Write([]byte(msg + "\r\n"))
} }
// WriteLines writes multiple messages
func (c *Client) WriteLines(msg []string) { func (c *Client) WriteLines(msg []string) {
for _, line := range msg { for _, line := range msg {
c.Write(line) c.Write(line)
} }
} }
// Send sends the given message
func (c *Client) Send(msg string) { func (c *Client) Send(msg string) {
if len(msg) > MAX_MSG_LENGTH { if len(msg) > MaxMsgLength {
return return
} }
select { select {
@ -111,21 +127,25 @@ func (c *Client) Send(msg string) {
} }
} }
// SendLines sends multiple messages
func (c *Client) SendLines(msg []string) { func (c *Client) SendLines(msg []string) {
for _, line := range msg { for _, line := range msg {
c.Send(line) c.Send(line)
} }
} }
// IsSilenced checks if the client is silenced
func (c *Client) IsSilenced() bool { func (c *Client) IsSilenced() bool {
return c.silencedUntil.After(time.Now()) return c.silencedUntil.After(time.Now())
} }
// Silence silences a client for the given duration
func (c *Client) Silence(d time.Duration) { func (c *Client) Silence(d time.Duration) {
c.silencedUntil = time.Now().Add(d) c.silencedUntil = time.Now().Add(d)
} }
func (c *Client) Resize(width int, height int) error { // Resize resizes the client to the given width and height
func (c *Client) Resize(width, height int) error {
err := c.term.SetSize(width, height) err := c.term.SetSize(width, height)
if err != nil { if err != nil {
logger.Errorf("Resize failed: %dx%d", width, height) logger.Errorf("Resize failed: %dx%d", width, height)
@ -135,11 +155,12 @@ func (c *Client) Resize(width int, height int) error {
return nil return nil
} }
// Rename renames the client to the given name
func (c *Client) Rename(name string) { func (c *Client) Rename(name string) {
c.Name = name c.Name = name
var prompt string var prompt string
if(c.colorMe) { if c.colorMe {
prompt = c.ColoredName() prompt = c.ColoredName()
} else { } else {
prompt = c.Name prompt = c.Name
@ -148,6 +169,7 @@ func (c *Client) Rename(name string) {
c.term.SetPrompt(fmt.Sprintf("[%s] ", prompt)) c.term.SetPrompt(fmt.Sprintf("[%s] ", prompt))
} }
// Fingerprint returns the fingerprint
func (c *Client) Fingerprint() string { func (c *Client) Fingerprint() string {
return c.Conn.Permissions.Extensions["fingerprint"] return c.Conn.Permissions.Extensions["fingerprint"]
} }
@ -188,12 +210,12 @@ func (c *Client) handleShell(channel ssh.Channel) {
case "/exit": case "/exit":
channel.Close() channel.Close()
case "/help": case "/help":
c.WriteLines(strings.Split(HELP_TEXT, "\n")) c.WriteLines(strings.Split(HelpText, "\n"))
if c.Server.IsOp(c) { if c.Server.IsOp(c) {
c.WriteLines(strings.Split(OP_HELP_TEXT, "\n")) c.WriteLines(strings.Split(OpHelpText, "\n"))
} }
case "/about": case "/about":
c.WriteLines(strings.Split(ABOUT_TEXT, "\n")) c.WriteLines(strings.Split(AboutText, "\n"))
case "/uptime": case "/uptime":
c.Write(c.Server.Uptime()) c.Write(c.Server.Uptime())
case "/beep": case "/beep":
@ -224,7 +246,7 @@ func (c *Client) handleShell(channel ssh.Channel) {
if len(parts) == 2 { if len(parts) == 2 {
client := c.Server.Who(parts[1]) client := c.Server.Who(parts[1])
if client != nil { if client != nil {
version := RE_STRIP_TEXT.ReplaceAllString(string(client.Conn.ClientVersion()), "") version := reStripText.ReplaceAllString(string(client.Conn.ClientVersion()), "")
if len(version) > 100 { if len(version) > 100 {
version = "Evil Jerk with a superlong string" version = "Evil Jerk with a superlong string"
} }
@ -239,7 +261,7 @@ func (c *Client) handleShell(channel ssh.Channel) {
names := "" names := ""
nameList := c.Server.List(nil) nameList := c.Server.List(nil)
for _, name := range nameList { for _, name := range nameList {
names += c.Server.Who(name).ColoredName() + SYSTEM_MESSAGE_FORMAT + ", " names += c.Server.Who(name).ColoredName() + systemMessageFormat + ", "
} }
if len(names) > 2 { if len(names) > 2 {
names = names[:len(names)-2] names = names[:len(names)-2]
@ -317,7 +339,7 @@ func (c *Client) handleShell(channel ssh.Channel) {
if !c.Server.IsOp(c) { if !c.Server.IsOp(c) {
c.SysMsg("You're not an admin.") c.SysMsg("You're not an admin.")
} else { } else {
var split []string = strings.SplitN(line, " ", 2) var split = strings.SplitN(line, " ", 2)
var msg string var msg string
if len(split) > 1 { if len(split) > 1 {
msg = split[1] msg = split[1]
@ -351,7 +373,7 @@ func (c *Client) handleShell(channel ssh.Channel) {
c.Server.MotdUnicast(c) c.Server.MotdUnicast(c)
} else { } else {
var newmotd string var newmotd string
if (len(parts) == 2) { if len(parts) == 2 {
newmotd = parts[1] newmotd = parts[1]
} else { } else {
newmotd = parts[1] + " " + parts[2] newmotd = parts[1] + " " + parts[2]
@ -393,7 +415,7 @@ func (c *Client) handleShell(channel ssh.Channel) {
msg := fmt.Sprintf("%s: %s", c.ColoredName(), line) msg := fmt.Sprintf("%s: %s", c.ColoredName(), line)
/* Rate limit */ /* Rate limit */
if time.Now().Sub(c.lastTX) < REQUIRED_WAIT { if time.Now().Sub(c.lastTX) < RequiredWait {
c.SysMsg("Rate limiting in effect.") c.SysMsg("Rate limiting in effect.")
continue continue
} }

3
cmd.go
View File

@ -4,15 +4,16 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"strings"
"os" "os"
"os/signal" "os/signal"
"strings"
"github.com/alexcesaro/log" "github.com/alexcesaro/log"
"github.com/alexcesaro/log/golog" "github.com/alexcesaro/log/golog"
"github.com/jessevdk/go-flags" "github.com/jessevdk/go-flags"
) )
// Options contains the flag options
type Options struct { type Options struct {
Verbose []bool `short:"v" long:"verbose" description:"Show verbose logging."` 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"` Identity string `short:"i" long:"identity" description:"Private key to identify server with." default:"~/.ssh/id_rsa"`

View File

@ -2,48 +2,70 @@ package main
import ( import (
"fmt" "fmt"
"strings" "math/rand"
"math/rand"
"time"
"regexp" "regexp"
"strings"
"time"
) )
const RESET string = "\033[0m" const (
const BOLD string = "\033[1m" // Reset resets the color
const DIM string = "\033[2m" Reset = "\033[0m"
const ITALIC string = "\033[3m"
const UNDERLINE string = "\033[4m"
const BLINK string = "\033[5m"
const INVERT string = "\033[7m"
var colors = []string { "31", "32", "33", "34", "35", "36", "37", "91", "92", "93", "94", "95", "96", "97" } // Bold makes the following text bold
// For removing ANSI Escapes Bold = "\033[1m"
var deColor *regexp.Regexp = regexp.MustCompile("\033\\[[\\d;]+m")
// Dim dims the following text
Dim = "\033[2m"
// Italic makes the following text italic
Italic = "\033[3m"
// Underline underlines the following text
Underline = "\033[4m"
// Blink blinks the following text
Blink = "\033[5m"
// Invert inverts the following text
Invert = "\033[7m"
)
var colors = []string{"31", "32", "33", "34", "35", "36", "37", "91", "92", "93", "94", "95", "96", "97"}
// deColor is used for removing ANSI Escapes
var deColor = regexp.MustCompile("\033\\[[\\d;]+m")
// DeColorString removes all color from the given string
func DeColorString(s string) string { func DeColorString(s string) string {
s = deColor.ReplaceAllString(s, "") s = deColor.ReplaceAllString(s, "")
return s return s
} }
// RandomColor256 returns a random (of 256) color
func RandomColor256() string { func RandomColor256() string {
return fmt.Sprintf("38;05;%d", rand.Intn(256)) return fmt.Sprintf("38;05;%d", rand.Intn(256))
} }
// RandomColor returns a random color
func RandomColor() string { func RandomColor() string {
return colors[rand.Intn(len(colors))] return colors[rand.Intn(len(colors))]
} }
// ColorString returns a message in the given color
func ColorString(color string, msg string) string { func ColorString(color string, msg string) string {
return BOLD + "\033[" + color + "m" + msg + RESET return Bold + "\033[" + color + "m" + msg + Reset
} }
// RandomColorInit initializes the random seed
func RandomColorInit() { func RandomColorInit() {
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
} }
// Horrible hack to "continue" the previous string color and format // ContinuousFormat is a horrible hack to "continue" the previous string color
// after a RESET has been encountered. // and format after a RESET has been encountered.
//
// This is not HTML where you can just do a </style> to resume your previous formatting! // This is not HTML where you can just do a </style> to resume your previous formatting!
func ContinuousFormat(format string, str string) string { func ContinuousFormat(format string, str string) string {
return SYSTEM_MESSAGE_FORMAT + strings.Replace(str, RESET, format, -1) + RESET return systemMessageFormat + strings.Replace(str, Reset, format, -1) + Reset
} }

View File

@ -3,6 +3,7 @@ package main
import "sync" import "sync"
// History contains the history entries
type History struct { type History struct {
entries []string entries []string
head int head int
@ -10,12 +11,14 @@ type History struct {
lock sync.Mutex lock sync.Mutex
} }
// NewHistory constructs a new history of the given size
func NewHistory(size int) *History { func NewHistory(size int) *History {
return &History{ return &History{
entries: make([]string, size), entries: make([]string, size),
} }
} }
// Add adds the given entry to the entries in the history
func (h *History) Add(entry string) { func (h *History) Add(entry string) {
h.lock.Lock() h.lock.Lock()
defer h.lock.Unlock() defer h.lock.Unlock()
@ -28,10 +31,12 @@ func (h *History) Add(entry string) {
} }
} }
// Len returns the number of entries in the history
func (h *History) Len() int { func (h *History) Len() int {
return h.size return h.size
} }
// Get the entry with the given number
func (h *History) Get(num int) []string { func (h *History) Get(num int) []string {
h.lock.Lock() h.lock.Lock()
defer h.lock.Unlock() defer h.lock.Unlock()

View File

@ -13,17 +13,22 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
const MAX_NAME_LENGTH = 32 const (
const HISTORY_LEN = 20 maxNameLength = 32
historyLength = 20
systemMessageFormat = "\033[1;3;90m"
privateMessageFormat = "\033[3m"
beep = "\007"
)
const SYSTEM_MESSAGE_FORMAT string = "\033[1;3;90m" var (
const PRIVATE_MESSAGE_FORMAT string = "\033[3m" reStripText = regexp.MustCompile("[^0-9A-Za-z_.-]")
const BEEP string = "\007" )
var RE_STRIP_TEXT = regexp.MustCompile("[^0-9A-Za-z_.-]")
// Clients is a map of clients
type Clients map[string]*Client type Clients map[string]*Client
// Server holds all the fields used by a server
type Server struct { type Server struct {
sshConfig *ssh.ServerConfig sshConfig *ssh.ServerConfig
done chan struct{} done chan struct{}
@ -33,12 +38,12 @@ type Server struct {
motd string motd string
whitelist map[string]struct{} // fingerprint lookup whitelist map[string]struct{} // fingerprint lookup
admins map[string]struct{} // fingerprint lookup admins map[string]struct{} // fingerprint lookup
bannedPk map[string]*time.Time // fingerprint lookup bannedPK map[string]*time.Time // fingerprint lookup
bannedIp map[net.Addr]*time.Time
started time.Time started time.Time
sync.Mutex sync.Mutex
} }
// NewServer constructs a new server
func NewServer(privateKey []byte) (*Server, error) { func NewServer(privateKey []byte) (*Server, error) {
signer, err := ssh.ParsePrivateKey(privateKey) signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil { if err != nil {
@ -49,12 +54,11 @@ func NewServer(privateKey []byte) (*Server, error) {
done: make(chan struct{}), done: make(chan struct{}),
clients: Clients{}, clients: Clients{},
count: 0, count: 0,
history: NewHistory(HISTORY_LEN), history: NewHistory(historyLength),
motd: "Message of the Day! Modify with /motd", motd: "Message of the Day! Modify with /motd",
whitelist: map[string]struct{}{}, whitelist: map[string]struct{}{},
admins: map[string]struct{}{}, admins: map[string]struct{}{},
bannedPk: map[string]*time.Time{}, bannedPK: map[string]*time.Time{},
bannedIp: map[net.Addr]*time.Time{},
started: time.Now(), started: time.Now(),
} }
@ -80,14 +84,17 @@ func NewServer(privateKey []byte) (*Server, error) {
return &server, nil return &server, nil
} }
// Len returns the number of clients
func (s *Server) Len() int { func (s *Server) Len() int {
return len(s.clients) return len(s.clients)
} }
// SysMsg broadcasts the given message to everyone
func (s *Server) SysMsg(msg string, args ...interface{}) { func (s *Server) SysMsg(msg string, args ...interface{}) {
s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, " * "+fmt.Sprintf(msg, args...)), nil) 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) { func (s *Server) Broadcast(msg string, except *Client) {
logger.Debugf("Broadcast to %d: %s", s.Len(), msg) logger.Debugf("Broadcast to %d: %s", s.Len(), msg)
s.history.Add(msg) s.history.Add(msg)
@ -99,45 +106,49 @@ func (s *Server) Broadcast(msg string, except *Client) {
if strings.Contains(msg, client.Name) { if strings.Contains(msg, client.Name) {
// Turn message red if client's name is mentioned, and send BEL if they have enabled beeping // Turn message red if client's name is mentioned, and send BEL if they have enabled beeping
tmpMsg := strings.Split(msg, RESET) tmpMsg := strings.Split(msg, Reset)
if client.beepMe { if client.beepMe {
tmpMsg[0] += BEEP tmpMsg[0] += beep
} }
client.Send(strings.Join(tmpMsg, RESET + BOLD + "\033[31m") + RESET) client.Send(strings.Join(tmpMsg, Reset+Bold+"\033[31m") + Reset)
} else { } else {
client.Send(msg) client.Send(msg)
} }
} }
} }
/* Send a message to a particular nick, if it exists */ // Privmsg sends a message to a particular nick, if it exists
func (s *Server) Privmsg(nick, message string, sender *Client) error { func (s *Server) Privmsg(nick, message string, sender *Client) error {
/* Get the recipient */ // Get the recipient
target, ok := s.clients[nick] target, ok := s.clients[nick]
if !ok { if !ok {
return fmt.Errorf("no client with that nick") return fmt.Errorf("no client with that nick")
} }
/* Send the message */ // Send the message
target.Msg <- fmt.Sprintf(BEEP+"[PM from %v] %s%v%s", sender.ColoredName(), PRIVATE_MESSAGE_FORMAT, message, RESET) target.Msg <- fmt.Sprintf(beep+"[PM from %v] %s%v%s", sender.ColoredName(), privateMessageFormat, message, Reset)
logger.Debugf("PM from %v to %v: %v", sender.Name, nick, message) logger.Debugf("PM from %v to %v: %v", sender.Name, nick, message)
return nil return nil
} }
// SetMotd sets the Message of the Day (MOTD)
func (s *Server) SetMotd(motd string) { func (s *Server) SetMotd(motd string) {
s.Lock() s.Lock()
s.motd = motd s.motd = motd
s.Unlock() s.Unlock()
} }
// MotdUnicast sends the MOTD as a SysMsg
func (s *Server) MotdUnicast(client *Client) { func (s *Server) MotdUnicast(client *Client) {
client.SysMsg("MOTD:\r\n" + ColorString("36", s.motd)) /* a nice cyan color */ client.SysMsg("MOTD:\r\n" + ColorString("36", s.motd)) /* a nice cyan color */
} }
// MotdBroadcast broadcasts the MOTD
func (s *Server) MotdBroadcast(client *Client) { func (s *Server) MotdBroadcast(client *Client) {
s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client) s.Broadcast(ContinuousFormat(systemMessageFormat, fmt.Sprintf(" * New MOTD set by %s.", client.ColoredName())), client)
s.Broadcast(ColorString("36", s.motd), client) s.Broadcast(ColorString("36", s.motd), client)
} }
// Add adds the client to the list of clients
func (s *Server) Add(client *Client) { func (s *Server) Add(client *Client) {
go func() { go func() {
s.MotdUnicast(client) s.MotdUnicast(client)
@ -158,9 +169,10 @@ func (s *Server) Add(client *Client) {
num := len(s.clients) num := len(s.clients)
s.Unlock() s.Unlock()
s.Broadcast(ContinuousFormat(SYSTEM_MESSAGE_FORMAT, 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)
} }
// Remove removes the given client from the list of clients
func (s *Server) Remove(client *Client) { func (s *Server) Remove(client *Client) {
s.Lock() s.Lock()
delete(s.clients, client.Name) delete(s.clients, client.Name)
@ -172,23 +184,24 @@ func (s *Server) Remove(client *Client) {
func (s *Server) proposeName(name string) (string, error) { func (s *Server) proposeName(name string) (string, error) {
// Assumes caller holds lock. // Assumes caller holds lock.
var err error var err error
name = RE_STRIP_TEXT.ReplaceAllString(name, "") name = reStripText.ReplaceAllString(name, "")
if len(name) > MAX_NAME_LENGTH { if len(name) > maxNameLength {
name = name[:MAX_NAME_LENGTH] name = name[:maxNameLength]
} else if len(name) == 0 { } else if len(name) == 0 {
name = fmt.Sprintf("Guest%d", s.count) name = fmt.Sprintf("Guest%d", s.count)
} }
_, collision := s.clients[name] _, collision := s.clients[name]
if collision { if collision {
err = fmt.Errorf("%s is not available.", name) err = fmt.Errorf("%s is not available", name)
name = fmt.Sprintf("Guest%d", s.count) name = fmt.Sprintf("Guest%d", s.count)
} }
return name, err return name, err
} }
// Rename renames the given client (user)
func (s *Server) Rename(client *Client, newName string) { func (s *Server) Rename(client *Client, newName string) {
s.Lock() s.Lock()
@ -209,10 +222,11 @@ func (s *Server) Rename(client *Client, newName string) {
s.SysMsg("%s is now known as %s.", ColorString(client.Color, oldName), ColorString(client.Color, newName)) s.SysMsg("%s is now known as %s.", ColorString(client.Color, oldName), ColorString(client.Color, newName))
} }
// List lists the clients with the given prefix
func (s *Server) List(prefix *string) []string { func (s *Server) List(prefix *string) []string {
r := []string{} r := []string{}
for name, _ := range s.clients { for name := range s.clients {
if prefix != nil && !strings.HasPrefix(name, *prefix) { if prefix != nil && !strings.HasPrefix(name, *prefix) {
continue continue
} }
@ -222,10 +236,12 @@ func (s *Server) List(prefix *string) []string {
return r return r
} }
// Who returns the client with a given name
func (s *Server) Who(name string) *Client { func (s *Server) Who(name string) *Client {
return s.clients[name] return s.clients[name]
} }
// Op adds the given fingerprint to the list of admins
func (s *Server) Op(fingerprint string) { func (s *Server) Op(fingerprint string) {
logger.Infof("Adding admin: %s", fingerprint) logger.Infof("Adding admin: %s", fingerprint)
s.Lock() s.Lock()
@ -233,6 +249,7 @@ func (s *Server) Op(fingerprint string) {
s.Unlock() s.Unlock()
} }
// Whitelist adds the given fingerprint to the whitelist
func (s *Server) Whitelist(fingerprint string) { func (s *Server) Whitelist(fingerprint string) {
logger.Infof("Adding whitelist: %s", fingerprint) logger.Infof("Adding whitelist: %s", fingerprint)
s.Lock() s.Lock()
@ -240,15 +257,18 @@ func (s *Server) Whitelist(fingerprint string) {
s.Unlock() s.Unlock()
} }
// Uptime returns the time since the server was started
func (s *Server) Uptime() string { func (s *Server) Uptime() string {
return time.Now().Sub(s.started).String() return time.Now().Sub(s.started).String()
} }
// IsOp checks if the given client is Op
func (s *Server) IsOp(client *Client) bool { func (s *Server) IsOp(client *Client) bool {
_, r := s.admins[client.Fingerprint()] _, r := s.admins[client.Fingerprint()]
return r return r
} }
// IsWhitelisted checks if the given fingerprint is whitelisted
func (s *Server) IsWhitelisted(fingerprint string) bool { func (s *Server) IsWhitelisted(fingerprint string) bool {
/* if no whitelist, anyone is welcome */ /* if no whitelist, anyone is welcome */
if len(s.whitelist) == 0 { if len(s.whitelist) == 0 {
@ -260,8 +280,9 @@ func (s *Server) IsWhitelisted(fingerprint string) bool {
return r return r
} }
// IsBanned checks if the given fingerprint is banned
func (s *Server) IsBanned(fingerprint string) bool { func (s *Server) IsBanned(fingerprint string) bool {
ban, hasBan := s.bannedPk[fingerprint] ban, hasBan := s.bannedPK[fingerprint]
if !hasBan { if !hasBan {
return false return false
} }
@ -275,6 +296,7 @@ func (s *Server) IsBanned(fingerprint string) bool {
return true return true
} }
// Ban bans a fingerprint for the given duration
func (s *Server) Ban(fingerprint string, duration *time.Duration) { func (s *Server) Ban(fingerprint string, duration *time.Duration) {
var until *time.Time var until *time.Time
s.Lock() s.Lock()
@ -282,16 +304,18 @@ func (s *Server) Ban(fingerprint string, duration *time.Duration) {
when := time.Now().Add(*duration) when := time.Now().Add(*duration)
until = &when until = &when
} }
s.bannedPk[fingerprint] = until s.bannedPK[fingerprint] = until
s.Unlock() s.Unlock()
} }
// Unban unbans a banned fingerprint
func (s *Server) Unban(fingerprint string) { func (s *Server) Unban(fingerprint string) {
s.Lock() s.Lock()
delete(s.bannedPk, fingerprint) delete(s.bannedPK, fingerprint)
s.Unlock() s.Unlock()
} }
// Start starts the server
func (s *Server) Start(laddr string) error { func (s *Server) Start(laddr string) error {
// Once a ServerConfig has been configured, connections can be // Once a ServerConfig has been configured, connections can be
// accepted. // accepted.
@ -324,7 +348,7 @@ func (s *Server) Start(laddr string) error {
return return
} }
version := RE_STRIP_TEXT.ReplaceAllString(string(sshConn.ClientVersion()), "") version := reStripText.ReplaceAllString(string(sshConn.ClientVersion()), "")
if len(version) > 100 { if len(version) > 100 {
version = "Evil Jerk with a superlong string" version = "Evil Jerk with a superlong string"
} }
@ -346,6 +370,7 @@ func (s *Server) Start(laddr string) error {
return nil return nil
} }
// AutoCompleteFunction handles auto completion of nicks
func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine string, newPos int, ok bool) { func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
if key == 9 { if key == 9 {
shortLine := strings.Split(line[:pos], " ") shortLine := strings.Split(line[:pos], " ")
@ -372,6 +397,7 @@ func (s *Server) AutoCompleteFunction(line string, pos int, key rune) (newLine s
return return
} }
// Stop stops the server
func (s *Server) Stop() { func (s *Server) Stop() {
for _, client := range s.clients { for _, client := range s.clients {
client.Conn.Close() client.Conn.Close()
@ -380,6 +406,7 @@ func (s *Server) Stop() {
close(s.done) close(s.done)
} }
// Fingerprint returns the fingerprint based on a public key
func Fingerprint(k ssh.PublicKey) string { func Fingerprint(k ssh.PublicKey) string {
hash := md5.Sum(k.Marshal()) hash := md5.Sum(k.Marshal())
r := fmt.Sprintf("% x", hash) r := fmt.Sprintf("% x", hash)