updated to new names/urls
This commit is contained in:
150
irc/client.go
Normal file
150
irc/client.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func newIrcClient(conn net.Conn, log *logrus.Logger, mc chan *ircMessage, s *ircd) *ircClient {
|
||||
c := new(ircClient)
|
||||
c.Id = uuid.New().String()
|
||||
c.conn = conn
|
||||
c.log = log
|
||||
c.mc = mc
|
||||
c.server = s
|
||||
c.inputBytes = new(bytes.Buffer)
|
||||
c.outputBytes = new(bytes.Buffer)
|
||||
c.session = NewIrcUserSession()
|
||||
return c
|
||||
}
|
||||
|
||||
type ircClient struct {
|
||||
//FIXME add a mutex and protect during writes
|
||||
Id string
|
||||
conn net.Conn
|
||||
log *logrus.Logger
|
||||
session *ircUserSession
|
||||
inputBytes *bytes.Buffer
|
||||
outputBytes *bytes.Buffer
|
||||
mc chan *ircMessage
|
||||
server *ircd
|
||||
}
|
||||
|
||||
func (c *ircClient) ServerName() string {
|
||||
return c.server.serverName
|
||||
}
|
||||
|
||||
func (c *ircClient) Kill() {
|
||||
//server will remove it from the server-managed client list
|
||||
//and will call c.CleanupAndClose() when it pops from this channel
|
||||
c.server.deadClients <- c
|
||||
}
|
||||
|
||||
func (c *ircClient) CleanupAndClose() {
|
||||
//client cleanup
|
||||
//FIXME truncate client buffers and whatnot here
|
||||
// server object already handles removing it from the list of clients
|
||||
// for the server, so don't do it here
|
||||
c.log.Infof("client=%s cleanup", c.Id)
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *ircClient) AppendStringToOutputBuffer(input string) {
|
||||
// woo boilerplate because no polymorphism
|
||||
c.AppendBytesToOutputBuffer([]byte(input))
|
||||
}
|
||||
|
||||
func (c *ircClient) AppendBytesToOutputBuffer(input []byte) {
|
||||
c.outputBytes.Write(input)
|
||||
}
|
||||
|
||||
func (c *ircClient) FlushOutputBuffer() bool {
|
||||
numBytes, err := c.outputBytes.WriteTo(c.conn)
|
||||
if err != nil {
|
||||
c.log.Debugf("failed to write completely to client<%s>, marking dead", c.Id)
|
||||
c.Kill()
|
||||
return false
|
||||
} else {
|
||||
c.log.Debugf("wrote %d bytes to client<%s>", numBytes, c.Id)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ircClient) RespUnknownCommand(command string) {
|
||||
//421 == unknown command
|
||||
c.SendServerMessage(421, []string{command, "Unknown command. Please try your call again later."})
|
||||
}
|
||||
|
||||
func (c *ircClient) SendServerMessage(code uint16, params []string) bool {
|
||||
//FIXME this is terrible hacky duct tape code, redo it
|
||||
p := ""
|
||||
if len(params) > 0 {
|
||||
// pop off last item for separate processing
|
||||
lastelem := params[len(params)-1]
|
||||
params = params[:len(params)-1]
|
||||
|
||||
for _, elem := range params {
|
||||
if strings.ContainsAny(elem, " \t\r\n") {
|
||||
c.log.Errorf("wtf %s", elem)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ContainsAny(lastelem, "\t\r\n") {
|
||||
c.log.Errorf("wtf %s", lastelem)
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.ContainsAny(lastelem, " ") {
|
||||
lastelem = fmt.Sprintf(":%s", lastelem)
|
||||
}
|
||||
p = strings.Join(params, " ")
|
||||
if len(p) > 0 {
|
||||
p = " " + lastelem
|
||||
} else {
|
||||
p = lastelem
|
||||
}
|
||||
}
|
||||
|
||||
sendstr := fmt.Sprintf(":%s %d %s", c.ServerName(), code, c.session.nick)
|
||||
if len(p) > 0 {
|
||||
sendstr = sendstr + " " + p
|
||||
}
|
||||
sendstr = sendstr + "\r\n"
|
||||
c.log.Debugf("sending string '%s' to client<%s>", sendstr, c.Id)
|
||||
c.AppendStringToOutputBuffer(sendstr)
|
||||
return c.FlushOutputBuffer()
|
||||
}
|
||||
|
||||
func (c *ircClient) AppendInputBuffer(input []byte) {
|
||||
// Read the incoming connection into the buffer.
|
||||
c.log.Printf("conn<%s>: got %d bytes from net", c.Id, len(input))
|
||||
c.inputBytes.Write(input)
|
||||
c.log.Debugf("conn<%s> buffer now %d bytes total", c.Id, c.inputBytes.Len())
|
||||
c.ParseInputBuffer()
|
||||
}
|
||||
|
||||
func (c *ircClient) ParseInputBuffer() {
|
||||
//FIXME update a timer here on the client when a line is parsed
|
||||
//successfully and time them out after a while
|
||||
c.log.Debugf("my input buffer is %d bytes", c.inputBytes.Len())
|
||||
c.log.Debugf("my input buffer is: '%s'", c.inputBytes.String())
|
||||
line, err := c.inputBytes.ReadString(byte('\n'))
|
||||
if err == nil {
|
||||
c.mc <- c.ParseSingleInput(line)
|
||||
c.ParseInputBuffer()
|
||||
} else {
|
||||
// error parsing input buffer, probably don't have a full line yet
|
||||
return
|
||||
}
|
||||
c.log.Debugf("my input buffer is %d bytes", c.inputBytes.Len())
|
||||
c.log.Debugf("my input buffer is: '%s'", c.inputBytes.String())
|
||||
}
|
||||
|
||||
func (c *ircClient) ParseSingleInput(line string) *ircMessage {
|
||||
return NewIrcMessageFromString(line, c)
|
||||
}
|
||||
100
irc/irc.go
Normal file
100
irc/irc.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package irc
|
||||
|
||||
func (s *ircd) processIRCMessage(m *ircMessage) {
|
||||
// FIXME put all of these in a map of string->function
|
||||
// and dispatch them that way instead
|
||||
switch m.command {
|
||||
case "CAP":
|
||||
s.processCAPCommand(m)
|
||||
case "NICK":
|
||||
s.processNICKCommand(m)
|
||||
case "USER":
|
||||
s.processUSERCommand(m)
|
||||
default:
|
||||
s.processUnknownCommand(m)
|
||||
}
|
||||
s.log.Infof("client<%s> sent %+v", m.from.Id, m)
|
||||
}
|
||||
|
||||
func (s *ircd) sayHello(c *ircClient) {
|
||||
//001 welcome
|
||||
//002 host/version decl
|
||||
//003, 004, 005, 251, 252, 253, 254, 255, 256, 266
|
||||
//375 (begin motd)
|
||||
//372 motd (repeating)
|
||||
//376 end motd
|
||||
// change user mode (from server) :sneak!sneak@butt9q8.a.b.IP MODE sneak :+x
|
||||
/*
|
||||
:irc.butt.es 001 sneak :Welcome to the Buttes IRC Network sneak!sneak@1.2.3.4
|
||||
:irc.butt.es 002 sneak :Your host is irc.butt.es, running version InspIRCd-3
|
||||
:irc.butt.es 003 sneak :This server was created 09:33:24 Aug 05 2019
|
||||
:irc.butt.es 004 sneak irc.butt.es InspIRCd-3 BHIRSWcghiorswxz ACFHIMNOPQRSTXYZbcefijklmnoprstvwz :FHIXYZbefjklovw
|
||||
:irc.butt.es 005 sneak ACCEPT=30 AWAYLEN=200 CALLERID=g CASEMAPPING=ascii CHANLIMIT=#:20 CHANMODES=IXYZbew,k,FHfjl,ACMNOPQRSTcimnprstz CHANNELLEN=64 CHANTYPES=# ELIST=CMNTU ESILENCE=CcdiNnPpTtx EXCEPTS=e EXTBAN=,ACGNOQRSTUacjmprz :are supported by this server
|
||||
:irc.butt.es 005 sneak HOSTLEN=64 INVEX=I KEYLEN=32 KICKLEN=255 LINELEN=512 MAXLIST=IXbew:512 MAXTARGETS=20 MODES=20 NAMESX NETWORK=Buttes NICKLEN=32 OPERLOG OVERRIDE :are supported by this server
|
||||
:irc.butt.es 005 sneak PREFIX=(ov)@+ REMOVE SAFELIST SECURELIST SILENCE=32 STATUSMSG=@+ TOPICLEN=500 USERIP USERLEN=11 VBANLIST WHOX :are supported by this server
|
||||
:irc.butt.es 251 sneak :There are 34 users and 33 invisible on 2 servers
|
||||
:irc.butt.es 252 sneak 2 :operator(s) online
|
||||
:irc.butt.es 253 sneak 1 :unknown connections
|
||||
:irc.butt.es 254 sneak 33 :channels formed
|
||||
:irc.butt.es 255 sneak :I have 60 clients and 1 servers
|
||||
:irc.butt.es 265 sneak :Current local users: 60 Max: 65
|
||||
:irc.butt.es 266 sneak :Current global users: 67 Max: 72
|
||||
:irc.butt.es 375 sneak :irc.butt.es message of the day
|
||||
:irc.butt.es 372 sneak :- ____ _ _ _____ _____ _____ ____
|
||||
:irc.butt.es 372 sneak :- | __ )| | | |_ _|_ _| ____/ ___|
|
||||
:irc.butt.es 372 sneak :- | _ \| | | | | | | | | _| \___ \
|
||||
|
||||
:irc.butt.es 376 sneak :End of message of the day.
|
||||
:irc.butt.es 396 sneak butt9q8.a.b.IP :is now your displayed host
|
||||
:sneak!sneak@butt9q8.a.b.IP MODE sneak :+x
|
||||
*/
|
||||
}
|
||||
|
||||
func (s *ircd) processUnknownCommand(m *ircMessage) {
|
||||
m.from.RespUnknownCommand(m.command)
|
||||
if m.from.session.CheckState("init") {
|
||||
// if they are sending weird commands before NICK/USER, just drop
|
||||
// them
|
||||
// FIXME send an error
|
||||
m.from.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ircd) processNICKCommand(m *ircMessage) {
|
||||
//FIXME check if nick is in use
|
||||
//FIXME check if nick is valid
|
||||
s.log.Infof("%+v", m)
|
||||
s.log.Infof("%+v", m.params)
|
||||
s.log.Infof("%+v", m.from)
|
||||
s.log.Infof("%+v", m.from.session)
|
||||
if len(m.params) == 1 {
|
||||
m.from.session.SetNick(m.params[0])
|
||||
s.log.Infof("conn<%s> NICK %s", m.from.Id, m.params[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ircd) processUSERCommand(m *ircMessage) {
|
||||
// FIXME fail/disconnect if nick not yet set
|
||||
// FIXME fail if run a second time
|
||||
// FIXME tbh the client connection should be an FSM that limits usage of
|
||||
// certain valid commands per-state
|
||||
//params: <username> <hostname> <servername> <realname>
|
||||
if !m.from.session.CheckState("init") {
|
||||
// can only do this when going init->normal
|
||||
// FIXME send an error
|
||||
m.from.Kill()
|
||||
}
|
||||
|
||||
if m.from.session.SetUserInfo(m.params) { //this changes the session state
|
||||
s.log.Infof("%+v", m.from.session)
|
||||
s.sayHello(m.from)
|
||||
} else {
|
||||
// FIXME send an error
|
||||
m.from.Kill() // bye felicia
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ircd) processCAPCommand(m *ircMessage) {
|
||||
s.log.Debugln("ignoring CAP command, unsupported")
|
||||
// pass
|
||||
}
|
||||
71
irc/message.go
Normal file
71
irc/message.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package irc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ircMessage struct {
|
||||
from *ircClient
|
||||
//received
|
||||
tags string
|
||||
source string
|
||||
raw string
|
||||
command string
|
||||
params []string
|
||||
}
|
||||
|
||||
func parseIrcLine(line string) (*ircMessage, error) {
|
||||
//FIXME do this at compile or start time instead of every message
|
||||
ircregex, err := regexp.Compile(`^(\@(\S+) )?(?:[:](\S+) )?(\S+)(?: ([^:].+?))?(?: [:](.+))?$`)
|
||||
|
||||
if err != nil {
|
||||
panic("can't happen")
|
||||
}
|
||||
|
||||
line = strings.TrimRight(line, "\r\n")
|
||||
|
||||
m := new(ircMessage)
|
||||
m.raw = line
|
||||
|
||||
if ircregex.MatchString(m.raw) == false {
|
||||
return nil, errors.New("parse error")
|
||||
}
|
||||
matches := ircregex.FindAllStringSubmatch(m.raw, -1)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return nil, errors.New("parse error")
|
||||
}
|
||||
|
||||
match := matches[0]
|
||||
fmt.Printf("%+v\n", match)
|
||||
fmt.Printf("%+v\n", len(match))
|
||||
m.tags = match[2]
|
||||
m.source = match[3]
|
||||
m.command = strings.ToUpper(match[4])
|
||||
if len(match[5]) > 0 {
|
||||
m.params = strings.Fields(match[5])
|
||||
}
|
||||
if len(match[6]) > 0 {
|
||||
m.params = append(m.params, match[6])
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func NewIrcMessageFromString(line string, from *ircClient) *ircMessage {
|
||||
|
||||
msg, err := parseIrcLine(line)
|
||||
msg.from = from
|
||||
|
||||
if err != nil {
|
||||
panic("wat")
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (m *ircMessage) String() string {
|
||||
return fmt.Sprintf("IRCMessage<%s>('%s')", m.command, strings.Join(m.params, ","))
|
||||
}
|
||||
208
irc/responses.go
Normal file
208
irc/responses.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package irc
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
type ircServerResponse struct {
|
||||
code uint16
|
||||
symbol string
|
||||
longtext string
|
||||
params *[]string
|
||||
}
|
||||
|
||||
func NewIrcServerResponse(code uint16, params *[]string) {
|
||||
codes := ircResponseCodes()
|
||||
r := new(ircServerResponse)
|
||||
r.code = code
|
||||
r.symbol = (*codes)[code]
|
||||
r.params = params
|
||||
}
|
||||
|
||||
func ircResponseCodes() *map[uint16]string {
|
||||
responseCodeList := map[uint16]string{
|
||||
1: "RPL_WELCOME",
|
||||
2: "RPL_YOURHOST",
|
||||
3: "RPL_CREATED",
|
||||
4: "RPL_MYINFO",
|
||||
5: "RPL_ISUPPORT",
|
||||
10: "RPL_BOUNCE",
|
||||
15: "RPL_MAP",
|
||||
17: "RPL_MAPEND",
|
||||
18: "RPL_MAPSTART",
|
||||
20: "RPL_HELLO",
|
||||
42: "RPL_YOURID",
|
||||
43: "RPL_SAVENICK",
|
||||
200: "RPL_TRACELINK",
|
||||
201: "RPL_TRACECONNECTING",
|
||||
202: "RPL_TRACEHANDSHAKE",
|
||||
203: "RPL_TRACEUNKNOWN",
|
||||
204: "RPL_TRACEOPERATOR",
|
||||
205: "RPL_TRACEUSER",
|
||||
206: "RPL_TRACESERVER",
|
||||
207: "RPL_TRACESERVICE",
|
||||
208: "RPL_TRACENEWTYPE",
|
||||
209: "RPL_TRACECLASS",
|
||||
211: "RPL_STATSLINKINFO",
|
||||
212: "RPL_STATSCOMMANDS",
|
||||
213: "RPL_STATSCLINE",
|
||||
214: "RPL_STATSNLINE",
|
||||
215: "RPL_STATSILINE",
|
||||
216: "RPL_STATSKLINE",
|
||||
217: "RPL_STATSQLINE",
|
||||
218: "RPL_STATSYLINE",
|
||||
219: "RPL_ENDOFSTATS",
|
||||
221: "RPL_UMODEIS",
|
||||
231: "RPL_SERVICEINFO",
|
||||
232: "RPL_ENDOFSERVICES",
|
||||
233: "RPL_SERVICE",
|
||||
234: "RPL_SERVLIST",
|
||||
235: "RPL_SERVLISTEND",
|
||||
239: "RPL_STATSIAUTH",
|
||||
240: "RPL_STATSVLINE",
|
||||
241: "RPL_STATSLLINE",
|
||||
242: "RPL_STATSUPTIME",
|
||||
243: "RPL_STATSOLINE",
|
||||
244: "RPL_STATSHLINE",
|
||||
245: "RPL_STATSSLINE",
|
||||
246: "RPL_STATSPING",
|
||||
247: "RPL_STATSBLINE",
|
||||
248: "RPL_STATSDEFINE",
|
||||
249: "RPL_STATSDEBUG",
|
||||
250: "RPL_STATSDLINE",
|
||||
251: "RPL_LUSERCLIENT",
|
||||
252: "RPL_LUSEROP",
|
||||
253: "RPL_LUSERUNKNOWN",
|
||||
254: "RPL_LUSERCHANNELS",
|
||||
255: "RPL_LUSERME",
|
||||
256: "RPL_ADMINME",
|
||||
257: "RPL_ADMINLOC1",
|
||||
258: "RPL_ADMINLOC2",
|
||||
259: "RPL_ADMINEMAIL",
|
||||
261: "RPL_TRACELOG",
|
||||
262: "RPL_TRACEEND",
|
||||
263: "RPL_TRYAGAIN",
|
||||
265: "RPL_LOCALUSERS",
|
||||
266: "RPL_GLOBALUSERS",
|
||||
300: "RPL_NONE",
|
||||
301: "RPL_AWAY",
|
||||
302: "RPL_USERHOST",
|
||||
303: "RPL_ISON",
|
||||
304: "RPL_TEXT",
|
||||
305: "RPL_UNAWAY",
|
||||
306: "RPL_NOWAWAY",
|
||||
311: "RPL_WHOISUSER",
|
||||
312: "RPL_WHOISSERVER",
|
||||
313: "RPL_WHOISOPERATOR",
|
||||
314: "RPL_WHOWASUSER",
|
||||
315: "RPL_ENDOFWHO",
|
||||
316: "RPL_WHOISCHANOP",
|
||||
317: "RPL_WHOISIDLE",
|
||||
318: "RPL_ENDOFWHOIS",
|
||||
319: "RPL_WHOISCHANNELS",
|
||||
321: "RPL_LISTSTART",
|
||||
322: "RPL_LIST",
|
||||
323: "RPL_LISTEND",
|
||||
324: "RPL_CHANNELMODEIS",
|
||||
325: "RPL_UNIQOPIS",
|
||||
331: "RPL_NOTOPIC",
|
||||
332: "RPL_TOPIC",
|
||||
333: "RPL_TOPIC_WHO_TIME",
|
||||
341: "RPL_INVITING",
|
||||
342: "RPL_SUMMONING",
|
||||
344: "RPL_REOPLIST",
|
||||
345: "RPL_ENDOFREOPLIST",
|
||||
346: "RPL_INVITELIST",
|
||||
347: "RPL_ENDOFINVITELIST",
|
||||
348: "RPL_EXCEPTLIST",
|
||||
349: "RPL_ENDOFEXCEPTLIST",
|
||||
351: "RPL_VERSION",
|
||||
352: "RPL_WHOREPLY",
|
||||
353: "RPL_NAMREPLY",
|
||||
361: "RPL_KILLDONE",
|
||||
362: "RPL_CLOSING",
|
||||
363: "RPL_CLOSEEND",
|
||||
364: "RPL_LINKS",
|
||||
365: "RPL_ENDOFLINKS",
|
||||
366: "RPL_ENDOFNAMES",
|
||||
367: "RPL_BANLIST",
|
||||
368: "RPL_ENDOFBANLIST",
|
||||
369: "RPL_ENDOFWHOWAS",
|
||||
371: "RPL_INFO",
|
||||
372: "RPL_MOTD",
|
||||
373: "RPL_INFOSTART",
|
||||
374: "RPL_ENDOFINFO",
|
||||
375: "RPL_MOTDSTART",
|
||||
376: "RPL_ENDOFMOTD",
|
||||
381: "RPL_YOUREOPER",
|
||||
382: "RPL_REHASHING",
|
||||
383: "RPL_YOURESERVICE",
|
||||
384: "RPL_MYPORTIS",
|
||||
385: "RPL_NOTOPERANYMORE",
|
||||
391: "RPL_TIME",
|
||||
392: "RPL_USERSSTART",
|
||||
393: "RPL_USERS",
|
||||
394: "RPL_ENDOFUSERS",
|
||||
395: "RPL_NOUSERS",
|
||||
401: "ERR_NOSUCHNICK",
|
||||
402: "ERR_NOSUCHSERVER",
|
||||
403: "ERR_NOSUCHCHANNEL",
|
||||
404: "ERR_CANNOTSENDTOCHAN",
|
||||
405: "ERR_TOOMANYCHANNELS",
|
||||
406: "ERR_WASNOSUCHNICK",
|
||||
407: "ERR_TOOMANYTARGETS",
|
||||
408: "ERR_NOSUCHSERVICE",
|
||||
409: "ERR_NOORIGIN",
|
||||
411: "ERR_NORECIPIENT",
|
||||
412: "ERR_NOTEXTTOSEND",
|
||||
413: "ERR_NOTOPLEVEL",
|
||||
414: "ERR_WILDTOPLEVEL",
|
||||
415: "ERR_BADMASK",
|
||||
416: "ERR_TOOMANYMATCHES",
|
||||
421: "ERR_UNKNOWNCOMMAND",
|
||||
422: "ERR_NOMOTD",
|
||||
423: "ERR_NOADMININFO",
|
||||
424: "ERR_FILEERROR",
|
||||
431: "ERR_NONICKNAMEGIVEN",
|
||||
432: "ERR_ERRONEOUSNICKNAME",
|
||||
433: "ERR_NICKNAMEINUSE",
|
||||
434: "ERR_SERVICENAMEINUSE",
|
||||
435: "ERR_SERVICECONFUSED",
|
||||
436: "ERR_NICKCOLLISION",
|
||||
437: "ERR_UNAVAILRESOURCE",
|
||||
441: "ERR_USERNOTINCHANNEL",
|
||||
442: "ERR_NOTONCHANNEL",
|
||||
443: "ERR_USERONCHANNEL",
|
||||
444: "ERR_NOLOGIN",
|
||||
445: "ERR_SUMMONDISABLED",
|
||||
446: "ERR_USERSDISABLED",
|
||||
451: "ERR_NOTREGISTERED",
|
||||
461: "ERR_NEEDMOREPARAMS",
|
||||
462: "ERR_ALREADYREGISTRED",
|
||||
463: "ERR_NOPERMFORHOST",
|
||||
464: "ERR_PASSWDMISMATCH",
|
||||
465: "ERR_YOUREBANNEDCREEP",
|
||||
466: "ERR_YOUWILLBEBANNED",
|
||||
467: "ERR_KEYSET",
|
||||
471: "ERR_CHANNELISFULL",
|
||||
472: "ERR_UNKNOWNMODE",
|
||||
473: "ERR_INVITEONLYCHAN",
|
||||
474: "ERR_BANNEDFROMCHAN",
|
||||
475: "ERR_BADCHANNELKEY",
|
||||
476: "ERR_BADCHANMASK",
|
||||
477: "ERR_NOCHANMODES",
|
||||
478: "ERR_BANLISTFULL",
|
||||
481: "ERR_NOPRIVILEGES",
|
||||
482: "ERR_CHANOPRIVSNEEDED",
|
||||
483: "ERR_CANTKILLSERVER",
|
||||
484: "ERR_RESTRICTED",
|
||||
485: "ERR_UNIQOPRIVSNEEDED",
|
||||
491: "ERR_NOOPERHOST",
|
||||
492: "ERR_NOSERVICEHOST",
|
||||
499: "ERR_STATSKLINE",
|
||||
501: "ERR_UMODEUNKNOWNFLAG",
|
||||
502: "ERR_USERSDONTMATCH",
|
||||
708: "RPL_ETRACEFULL",
|
||||
759: "RPL_ETRACEEND",
|
||||
}
|
||||
log.Fatalf("%+v", responseCodeList)
|
||||
return &responseCodeList
|
||||
}
|
||||
116
irc/server.go
Normal file
116
irc/server.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package irc
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
import "github.com/spf13/viper"
|
||||
import "net"
|
||||
|
||||
type ircd struct {
|
||||
Running bool
|
||||
serverName string
|
||||
log *logrus.Logger
|
||||
netName string
|
||||
ircPort uint16
|
||||
httpPort uint16
|
||||
ircListener net.Listener
|
||||
ircClients map[*ircClient]bool
|
||||
newClients chan *ircClient
|
||||
deadClients chan *ircClient
|
||||
messageQueue chan *ircMessage
|
||||
c *viper.Viper
|
||||
}
|
||||
|
||||
// FIXME pull these from the config
|
||||
const (
|
||||
CONN_HOST = "localhost"
|
||||
CONN_PORT = "6667"
|
||||
CONN_TYPE = "tcp"
|
||||
)
|
||||
|
||||
func New(config *viper.Viper) *ircd {
|
||||
s := new(ircd)
|
||||
s.Running = true
|
||||
s.ircClients = make(map[*ircClient]bool)
|
||||
s.c = config
|
||||
s.serverName = s.c.GetString("myhostname")
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ircd) SetLogger(logger *logrus.Logger) {
|
||||
s.log = logger
|
||||
}
|
||||
|
||||
func (s *ircd) SetServerName(name string) {
|
||||
s.serverName = name
|
||||
}
|
||||
|
||||
func (s *ircd) Start() {
|
||||
s.log.Infof(
|
||||
"sircd version %s (%s) built %s by %s starting.",
|
||||
s.c.GetString("version"),
|
||||
s.c.GetString("buildarch"),
|
||||
s.c.GetString("buildtime"),
|
||||
s.c.GetString("builduser"),
|
||||
)
|
||||
s.newClients = make(chan *ircClient, 128)
|
||||
s.deadClients = make(chan *ircClient, 128)
|
||||
s.messageQueue = make(chan *ircMessage, 128)
|
||||
|
||||
var err error
|
||||
s.ircListener, err = net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
|
||||
if err != nil {
|
||||
s.log.Fatalln("Error listening:", err.Error())
|
||||
}
|
||||
s.log.Println("Listening for irc proto on " + CONN_HOST + ":" + CONN_PORT)
|
||||
|
||||
go func() {
|
||||
conn, err := s.ircListener.Accept()
|
||||
if err != nil {
|
||||
s.log.Panicf("Error accepting: ", err.Error())
|
||||
}
|
||||
s.newClients <- newIrcClient(conn, s.log, s.messageQueue, s)
|
||||
}()
|
||||
|
||||
// FIXME have this do 'go s.handlemessages()' and process an input channel
|
||||
// of messages from all clients in its own goroutine, and then call
|
||||
// directly into the main loop here and only do client i/o in this
|
||||
// goroutine
|
||||
go s.processMessages()
|
||||
s.mainLoop()
|
||||
}
|
||||
|
||||
func (s *ircd) processMessages() {
|
||||
for {
|
||||
message := <-s.messageQueue
|
||||
s.processIRCMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ircd) mainLoop() {
|
||||
for {
|
||||
select {
|
||||
case client := <-s.newClients:
|
||||
s.ircClients[client] = true
|
||||
s.log.Println("new irc client: %s", client.Id)
|
||||
go func() {
|
||||
buf := make([]byte, 1024*1024)
|
||||
for {
|
||||
nbyte, err := client.conn.Read(buf)
|
||||
if err != nil {
|
||||
//this will surface it in the deadClients channel
|
||||
//reader below
|
||||
client.Kill()
|
||||
break
|
||||
} else {
|
||||
fragment := make([]byte, nbyte)
|
||||
copy(fragment, buf[:nbyte])
|
||||
// this will populate the message channel
|
||||
client.AppendInputBuffer(fragment)
|
||||
}
|
||||
}
|
||||
}()
|
||||
case deadClient := <-s.deadClients:
|
||||
delete(s.ircClients, deadClient)
|
||||
deadClient.CleanupAndClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
67
irc/session.go
Normal file
67
irc/session.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package irc
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
type ircUserSession struct {
|
||||
//FIXME add a mutex and protect during writes
|
||||
nick string
|
||||
user string
|
||||
specifiedHostname string
|
||||
realName string
|
||||
remoteHost string
|
||||
state string
|
||||
// FIXME make the connection check its state for invalidity periodically
|
||||
// and .Kill() it if the irc protocol session wants the client
|
||||
// connection gone
|
||||
invalid bool
|
||||
}
|
||||
|
||||
func NewIrcUserSession() *ircUserSession {
|
||||
// FIXME get conn.RemoteAddr passed in and stringify it here and put it
|
||||
// in the session
|
||||
s := new(ircUserSession)
|
||||
s.nick = "*" //default for s2c messages pre-NICK
|
||||
s.state = "init"
|
||||
s.invalid = false
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *ircUserSession) CheckState(wantedState string) bool {
|
||||
if s.state == wantedState {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ircUserSession) SetState(newState string) bool {
|
||||
log.Infof("state changing: %s->%s", s.state, newState) //FIXME remove this
|
||||
s.state = newState
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *ircUserSession) SetNick(input string) bool {
|
||||
// FIXME check for valid nick-ness
|
||||
s.nick = input
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *ircUserSession) SetUserInfo(params []string) bool {
|
||||
if !s.CheckState("init") {
|
||||
// can only do this when going init->normal
|
||||
return false
|
||||
}
|
||||
if s.nick == "*" {
|
||||
// must set nick first
|
||||
return false
|
||||
}
|
||||
if len(params) >= 4 {
|
||||
s.user = params[0]
|
||||
s.specifiedHostname = params[1]
|
||||
s.realName = params[3]
|
||||
s.SetState("normal")
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user