func (i *IRCServer) GetAuth(sessionid robust.Id) (string, error) {
	session, err := i.GetSession(sessionid)
	if err != nil {
		return "", err
	}
	return session.auth, nil
}
GetAuth returns the authentication string of |sessionid|, a random shared
secret between the RobustIRC network and the client to prevent session
hijacking.
package ircserver
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"fmt"
	"log"
	"math"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/robustirc/robustirc/internal/config"
	"github.com/robustirc/robustirc/internal/robust"
	"gopkg.in/sorcix/irc.v2"
)
const (
	maxNickLen    = "30"
	maxChannelLen = "32"
	letter = `\x41-\x5A\x61-\x7A`
	digit = `\x30-\x39`
	special = `\x5B-\x60\x7B-\x7D`
	chanstring = `\x01-\x06\x08-\x09\x0B-\x0C\x0E-\x1F\x21-\x2B\x2D-\x39\x3B-\xFF`
)
var (
	validNickRe    = regexp.MustCompile(`^[` + letter + special + `][` + letter + digit + special + `-]{0,` + maxNickLen + `}$`)
	validChannelRe = regexp.MustCompile(`^#[` + chanstring + `]{0,` + maxChannelLen + `}$`)
	ErrSessionNotYetSeen = errors.New("Session not yet seen")
	ErrNoSuchSession = errors.New("No such session")
	ErrSessionLimitReached = errors.New("MaxSessions limit reached")
	CursorEOF = errors.New("No more messages")
)
var (
	messagesProcessed = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Subsystem: "irc",
			Name:      "messages_processed",
			Help:      "Number of messages processed by message command",
		},
		[]string{"command"},
	)
	captchasVerified = prometheus.NewCounter(
		prometheus.CounterOpts{
			Subsystem: "captcha",
			Name:      "captchas_verified",
			Help:      "Number of CAPTCHAs successfully verified",
		},
	)
	captchasFailed = prometheus.NewCounter(
		prometheus.CounterOpts{
			Subsystem: "captcha",
			Name:      "captchas_failed",
			Help:      "Number of non-empty CAPTCHAs which failed verification",
		},
	)
)
func init() {
	prometheus.MustRegister(messagesProcessed)
	prometheus.MustRegister(captchasVerified)
	prometheus.MustRegister(captchasFailed)
}
type lcChan string
type lcNick string
type Session struct {
	Id                robust.Id
	auth              string
	loggedIn          bool
	Nick              string
	Username          string
	Realname          string
	Channels          map[lcChan]bool
	LastActivity      time.Time
	LastNonPing       time.Time
	LastSolvedCaptcha time.Time
	Operator          bool
	AwayMsg           string
	Created           int64
	throttlingExponent int
	invitedTo map[lcChan]bool
	modes ['z']bool
	svid string
	Pass string
	Server bool
	lastClientMessageId uint64
	ircPrefix irc.Prefix
	deleted bool
	RemoteAddr string  
}
func (s *Session) updateIrcPrefix() {
	s.ircPrefix = irc.Prefix{
		Name: s.Nick,
		User: s.Username,
		Host: fmt.Sprintf("robust/0x%x", s.Id.Id),
	}
}
const (
	chanop = iota
	voice
	maxChanMemberStatus
)
type banPattern struct {
	re      *regexp.Regexp
	pattern string
}
type channel struct {
	name string
	topicNick string
	topicTime time.Time
	topic     string
	nicks map[lcNick]*[maxChanMemberStatus]bool
	modes ['z']bool
	bans []banPattern
}
type svshold struct {
	added    time.Time
	duration time.Duration
	reason   string
}
type IRCServer struct {
	sessions   map[robust.Id]*Session
	sessionsMu *sync.RWMutex
	serverSessions []uint64
	nicks map[lcNick]*Session
	channels map[lcChan]*channel
	svsholds map[lcNick]svshold
	ServerPrefix *irc.Prefix
	lastProcessed   robust.Id
	lastProcessedMu *sync.RWMutex
	ServerCreation time.Time
	Config   config.Network
	ConfigMu *sync.RWMutex
}
func NewIRCServer(networkname string, serverCreation time.Time) *IRCServer {
	return &IRCServer{
		channels:        make(map[lcChan]*channel),
		svsholds:        make(map[lcNick]svshold),
		nicks:           make(map[lcNick]*Session),
		sessions:        make(map[robust.Id]*Session),
		sessionsMu:      &sync.RWMutex{},
		lastProcessedMu: &sync.RWMutex{},
		ServerPrefix:    &irc.Prefix{Name: networkname},
		ServerCreation:  serverCreation,
		Config:          config.DefaultConfig,
		ConfigMu:        &sync.RWMutex{},
	}
}
func (i *IRCServer) UpdateLastClientMessageID(msg *robust.Message) error {
	i.sessionsMu.Lock()
	defer i.sessionsMu.Unlock()
	session, err := i.getSessionLocked(msg.Session)
	if err != nil {
		return err
	}
	session.LastActivity = msg.Timestamp()
	if !strings.HasPrefix(strings.ToLower(msg.Data), "ping") {
		session.LastNonPing = session.LastActivity
	}
	session.lastClientMessageId = msg.ClientMessageId
	return nil
}
func (i *IRCServer) CreateSession(id robust.Id, auth string, timestamp time.Time) error {
	i.sessionsMu.Lock()
	defer i.sessionsMu.Unlock()
	return i.createSessionLocked(id, auth, timestamp)
}
func (i *IRCServer) createSessionLocked(id robust.Id, auth string, timestamp time.Time) error {
	if got, limit := uint64(len(i.sessions)), i.SessionLimit(); got >= limit && limit > 0 {
		return ErrSessionLimitReached
	}
	i.sessions[id] = &Session{
		Id:           id,
		auth:         auth,
		Channels:     make(map[lcChan]bool),
		invitedTo:    make(map[lcChan]bool),
		Created:      timestamp.UnixNano(),
		LastActivity: timestamp,
		LastNonPing:  timestamp,
		svid:         "0",
	}
	return nil
}
func (i *IRCServer) deleteSessionLocked(s *Session, msgid uint64) {
	for _, c := range i.channels {
		delete(c.nicks, NickToLower(s.Nick))
		i.maybeDeleteChannelLocked(c)
	}
	delete(i.nicks, NickToLower(s.Nick))
	s.deleted = true
}
func (i *IRCServer) ExpireSessions() []*robust.Message {
	var deletes []*robust.Message
	i.ConfigMu.RLock()
	defer i.ConfigMu.RUnlock()
	timeout := time.Duration(i.Config.SessionExpiration)
	i.sessionsMu.RLock()
	defer i.sessionsMu.RUnlock()
	for id, s := range i.sessions {
		if id.Reply != 0 {
			continue
		}
		if time.Since(s.LastActivity) <= timeout {
			continue
		}
		log.Printf("Expiring session %v (LastActivity: %v)", id, s.LastActivity)
		deletes = append(deletes, &robust.Message{
			Session: id,
			Type:    robust.DeleteSession,
			Data:    fmt.Sprintf("Ping timeout (%v)", timeout),
		})
	}
	return deletes
}
func IsValidNickname(nick string) bool {
	return validNickRe.MatchString(nick)
}
func IsServicesNickname(nick string) bool {
	return strings.HasSuffix(strings.ToLower(nick), "serv")
}
func IsValidChannel(channel string) bool {
	return validChannelRe.MatchString(channel)
}
var nickToLowerReplacer = strings.NewReplacer("[", "{", "]", "}", "\\", "|")
func NickToLower(nick string) lcNick {
	return lcNick(nickToLowerReplacer.Replace(strings.ToLower(nick)))
}
func ChanToLower(channelname string) lcChan {
	return lcChan(strings.ToLower(channelname))
}
func extractPassword(password, prefix string) string {
	var extracted string
	for _, part := range strings.Split(password, ":") {
		if strings.HasPrefix(strings.ToLower(part), prefix+"=") {
			extracted = part[len(prefix+"="):]
		}
		if !strings.HasPrefix(part, "nickserv=") &&
			!strings.HasPrefix(part, "services=") &&
			!strings.HasPrefix(part, "network=") &&
			!strings.HasPrefix(part, "session=") &&
			!strings.HasPrefix(part, "oper=") &&
			!strings.HasPrefix(part, "captcha=") &&
			extracted != "" {
			extracted = extracted + ":" + part
		}
	}
	return extracted
}
func (i *IRCServer) maybeDeleteChannelLocked(c *channel) {
	if len(c.nicks) > 0 {
		return
	}
	lc := ChanToLower(c.name)
	delete(i.channels, lc)
	for _, s := range i.sessions {
		delete(s.invitedTo, lc)
	}
}
func (i *IRCServer) Banned(remoteAddr string) string {
	i.ConfigMu.RLock()
	defer i.ConfigMu.RUnlock()
	return i.Config.Banned[remoteAddr]
}
func (i *IRCServer) ProcessMessage(msg *robust.Message, ircmsg *irc.Message) *Replyctx {
	i.sessionsMu.Lock()
	defer i.sessionsMu.Unlock()
	s := i.sessions[msg.Session]
	reply := &Replyctx{msgid: msg.Id.Id, session: s}
	if ircmsg == nil {
		i.sendUser(s, reply, &irc.Message{
			Prefix:  i.ServerPrefix,
			Command: irc.ERR_UNKNOWNCOMMAND,
			Params:  []string{s.Nick, "Unknown command"},
		})
		return reply
	}
	command := strings.ToUpper(ircmsg.Command)
	if msg.RemoteAddr != "" && msg.RemoteAddr != s.RemoteAddr {
		s.RemoteAddr = msg.RemoteAddr
		if reason := i.Banned(s.RemoteAddr); reason != "" {
			i.sendUser(s, reply, &irc.Message{
				Command: irc.ERROR,
				Params:  []string{"Closing Link: You are banned (" + reason + ")"},
			})
			i.deleteSessionLocked(s, reply.msgid)
			return reply
		}
	}
	messagesProcessed.WithLabelValues(command).Inc()
	if !s.loggedIn && !s.Server &&
		command != irc.NICK &&
		command != irc.USER &&
		command != irc.PASS &&
		command != irc.QUIT &&
		command != irc.SERVER {
		i.sendUser(s, reply, &irc.Message{
			Prefix:  i.ServerPrefix,
			Command: irc.ERR_NOTREGISTERED,
			Params:  []string{command, "You have not registered"},
		})
		if s.LastActivity.Sub(time.Unix(0, s.Created)) > 10*time.Minute {
			i.sendUser(s, reply, &irc.Message{
				Command: irc.ERROR,
				Params:  []string{"Closing Link: You have not registered within 10 minutes"},
			})
			i.deleteSessionLocked(s, reply.msgid)
		}
		return reply
	}
	var serverPrefix string
	if s.Server {
		serverPrefix = "server_"
	}
	cmd, ok := Commands[serverPrefix+command]
	if !ok {
		i.sendUser(s, reply, &irc.Message{
			Prefix:  i.ServerPrefix,
			Command: irc.ERR_UNKNOWNCOMMAND,
			Params:  []string{s.Nick, command, "Unknown command"},
		})
		return reply
	}
	if len(ircmsg.Params) < cmd.MinParams {
		i.sendUser(s, reply, &irc.Message{
			Prefix:  i.ServerPrefix,
			Command: irc.ERR_NEEDMOREPARAMS,
			Params:  []string{s.Nick, command, "Not enough parameters"},
		})
		return reply
	}
	cmd.Func(i, s, reply, ircmsg)
	return reply
}
func (i *IRCServer) SetLastProcessed(id robust.Id) {
	i.lastProcessedMu.Lock()
	defer i.lastProcessedMu.Unlock()
	i.lastProcessed = id
}
func (i *IRCServer) MaybeDeleteSession(session robust.Id) {
	i.sessionsMu.Lock()
	defer i.sessionsMu.Unlock()
	if s, ok := i.sessions[session]; ok {
		if s.Server || s.Operator {
			for id, session := range i.sessions {
				if session.deleted {
					delete(i.sessions, id)
				}
			}
		}
		if s.deleted {
			delete(i.sessions, session)
		}
	}
}
func (i *IRCServer) GetSession(id robust.Id) (*Session, error) {
	i.sessionsMu.RLock()
	defer i.sessionsMu.RUnlock()
	return i.getSessionLocked(id)
}
func (i *IRCServer) getSessionLocked(id robust.Id) (*Session, error) {
	s, ok := i.sessions[id]
	if ok {
		return s, nil
	}
	i.lastProcessedMu.RLock()
	defer i.lastProcessedMu.RUnlock()
	if i.lastProcessed.Id > id.Id {
		return nil, ErrNoSuchSession
	} else {
		return nil, ErrSessionNotYetSeen
	}
}