Merge pull request #51 from peterhellberg/lint-fix
Fix all lint issues (mainly ALL_CAPS and comments)
This commit is contained in:
commit
b81324a5b4
80
client.go
80
client.go
|
@ -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
3
cmd.go
|
@ -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"`
|
||||||
|
|
58
colors.go
58
colors.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
89
server.go
89
server.go
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue