diff --git a/client.go b/client.go deleted file mode 100644 index 5f1081e..0000000 --- a/client.go +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import ( - "fmt" - "net" - "strings" - "time" -) - -type Client struct { - server *Server - connection net.Conn - nick string - registered bool - connected bool - operator bool -} - -func (c *Client) disconnect() { - c.connected = false - c.connection.Close() -} - -func (c *Client) sendGlobalMessage(motd string) { - c.reply(rplMOTDStart) - - for len(motd) > 80 { - c.reply(rplMOTD, motd[:80]) - motd = motd[80:] - } - if len(motd) > 0 { - c.reply(rplMOTD, motd) - } - - c.reply(rplEndOfMOTD) -} - -//Send a reply to a user with the code specified -func (c *Client) reply(code replyCode, args ...string) { - if c.connected == false { - return - } - - switch code { - case rplWelcome: - c.write(fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name)) - case rplJoin: - c.write(fmt.Sprintf(":%s JOIN %s", args[0], args[1])) - case rplPart: - c.write(fmt.Sprintf(":%s PART %s %s", args[0], args[1], args[2])) - case rplTopic: - c.write(fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1])) - case rplNoTopic: - c.write(fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0])) - case rplNames: - c.write(fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1])) - case rplEndOfNames: - c.write(fmt.Sprintf(":%s 366 %s %s :End of NAMES list", c.server.name, c.nick, args[0])) - case rplNickChange: - c.write(fmt.Sprintf(":%s NICK %s", args[0], args[1])) - case rplKill: - c.write(fmt.Sprintf(":%s KILL %s A %s", args[0], c.nick, args[1])) - case rplMsg: - for _, itm := range strings.Split(args[2], "\n") { - c.write(fmt.Sprintf(":%s PRIVMSG %s %s", args[0], args[1], itm)) - } - case rplList: - c.write(fmt.Sprintf(":%s 322 %s %s", c.server.name, c.nick, args[0])) - case rplListEnd: - c.write(fmt.Sprintf(":%s 323 %s", c.server.name, c.nick)) - case rplOper: - c.write(fmt.Sprintf(":%s 381 %s :You are now an operator", c.server.name, c.nick)) - case rplChannelModeIs: - c.write(fmt.Sprintf(":%s 324 %s %s %s %s", c.server.name, c.nick, args[0], args[1], args[2])) - case rplKick: - c.write(fmt.Sprintf(":%s KICK %s %s %s", args[0], args[1], args[2], args[3])) - case rplInfo: - c.write(fmt.Sprintf(":%s 371 %s :%s", c.server.name, c.nick, args[0])) - case rplVersion: - c.write(fmt.Sprintf(":%s 351 %s %s", c.server.name, c.nick, args[0])) - case rplMOTDStart: - c.write(fmt.Sprintf(":%s 375 %s :- Message of the day - ", c.server.name, c.nick)) - case rplMOTD: - c.write(fmt.Sprintf(":%s 372 %s :- %s", c.server.name, c.nick, args[0])) - case rplEndOfMOTD: - c.write(fmt.Sprintf(":%s 376 %s :End of MOTD Command", c.server.name, c.nick)) - case rplPong: - c.write(fmt.Sprintf(":%s PONG %s %s", c.server.name, c.nick, c.server.name)) - case errMoreArgs: - c.write(fmt.Sprintf(":%s 461 %s :Not enough params", c.server.name, c.nick)) - case errNoNick: - c.write(fmt.Sprintf(":%s 431 %s :No nickname given", c.server.name, c.nick)) - case errInvalidNick: - c.write(fmt.Sprintf(":%s 432 %s %s :Erronenous nickname", c.server.name, c.nick, args[0])) - case errNickInUse: - c.write(fmt.Sprintf(":%s 433 %s %s :Nick already in use", c.server.name, c.nick, args[0])) - case errAlreadyReg: - c.write(fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name)) - case errNoSuchNick: - c.write(fmt.Sprintf(":%s 401 %s %s :No such nick/channel", c.server.name, c.nick, args[0])) - case errUnknownCommand: - c.write(fmt.Sprintf(":%s 421 %s %s :Unknown command", c.server.name, c.nick, args[0])) - case errNotReg: - c.write(fmt.Sprintf(":%s 451 :You have not registered", c.server.name)) - case errPassword: - c.write(fmt.Sprintf(":%s 464 %s :Error, password incorrect", c.server.name, c.nick)) - case errNoPriv: - c.write(fmt.Sprintf(":%s 481 %s :Permission denied", c.server.name, c.nick)) - case errCannotSend: - c.write(fmt.Sprintf(":%s 404 %s %s :Cannot send to channel", c.server.name, c.nick, args[0])) - } -} - -func (c *Client) write(output string) { - c.connection.SetWriteDeadline(time.Now().Add(time.Second * 30)) - if _, err := fmt.Fprintf(c.connection, "%s\r\n", output); err != nil { - c.disconnect() - return - } -} diff --git a/main.go b/main.go index cedbbdb..e00f902 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,14 @@ func main() { flag.Parse() + log.Printf("Listening on '%s'...", *ircAddress) + listener, err := net.Listen("tcp", *ircAddress) if err != nil { log.Printf(err.Error()) return } - log.Printf("Listening on %s", *ircAddress) - for { conn, err := listener.Accept() if err != nil { @@ -36,7 +36,7 @@ func main() { // The upstream connection doesn't get launched until we hear a nick // from the irc client - server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress)) - go server.RunClient(conn) + server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress), conn) + go server.RunWorker() } } diff --git a/server.go b/server.go index 0ebaf4d..27b3d06 100644 --- a/server.go +++ b/server.go @@ -11,58 +11,52 @@ import ( ) type Server struct { - eventChan chan Event - running bool - name string - client *Client - channel Channel // Single blessed channel + //eventChan chan Event + running bool + name string + + clientConn net.Conn + clientNick string + clientRegistered bool + clientOperator bool + upstreamLauncher libnmdc.HubConnectionOptions upstreamCloser chan struct{} upstream *libnmdc.HubConnection motd string } -func NewServer(name string, upstream libnmdc.HubAddress) *Server { +func NewServer(name string, upstream libnmdc.HubAddress, conn net.Conn) *Server { self := libnmdc.NewUserInfo("") self.ClientTag = APP_DESCRIPTION - return &Server{eventChan: make(chan Event), - name: name, - client: nil, - motd: "Connected to " + name, + return &Server{ + name: name, + clientConn: conn, + motd: "Connected to " + name, upstreamLauncher: libnmdc.HubConnectionOptions{ Address: upstream, Self: *self, }, upstreamCloser: make(chan struct{}, 1), - channel: Channel{ - clientMap: make(map[string]*Client), - modeMap: make(map[string]*ClientMode), - }, } } -func (s *Server) RunClient(conn net.Conn) { - - s.client = &Client{ - connection: conn, - connected: true, - server: s, // FIXME circular reference! - } +func (s *Server) RunWorker() { // Send the connection handshake - s.client.sendGlobalMessage(s.motd) + s.sendClientGlobalMessage(s.motd) // Can't connect to the upstream server yet, until we've recieved a nick. for { buf := make([]byte, CLIENT_READ_BUFFSIZE) - s.client.connection.SetReadDeadline(time.Now().Add(time.Second * CLIENT_READ_TIMEOUT_SEC)) - ln, err := s.client.connection.Read(buf) + s.clientConn.SetReadDeadline(time.Now().Add(time.Second * CLIENT_READ_TIMEOUT_SEC)) + ln, err := s.clientConn.Read(buf) if err != nil { if err == io.EOF { - s.client.disconnect() - if s.client != nil && s.client.registered { + s.DisconnectClient() + if s.clientConn != nil && s.clientRegistered { s.upstreamCloser <- struct{}{} } return // FIXME cleanup @@ -94,7 +88,7 @@ func (s *Server) RunClient(conn net.Conn) { } -func (s *Server) ProtocolReadLoop_NMDC(closeChan chan struct{}) { +func (s *Server) upstreamWorker() { // Initiate connection s.upstream = s.upstreamLauncher.Connect() @@ -102,7 +96,7 @@ func (s *Server) ProtocolReadLoop_NMDC(closeChan chan struct{}) { // Read loop for { select { - case <-closeChan: + case <-s.upstreamCloser: // Abandon the upstream connection s.upstream.Disconnect() return @@ -110,29 +104,29 @@ func (s *Server) ProtocolReadLoop_NMDC(closeChan chan struct{}) { case hubEvent := <-s.upstream.OnEvent: switch hubEvent.EventType { case libnmdc.EVENT_USER_JOINED: - s.client.reply(rplJoin, hubEvent.Nick, BLESSED_CHANNEL) + s.reply(rplJoin, hubEvent.Nick, BLESSED_CHANNEL) case libnmdc.EVENT_USER_PART: - s.client.reply(rplPart, hubEvent.Nick, BLESSED_CHANNEL, "Disconnected") + s.reply(rplPart, hubEvent.Nick, BLESSED_CHANNEL, "Disconnected") case libnmdc.EVENT_USER_UPDATED_INFO: // description change - no relevance for IRC users case libnmdc.EVENT_CONNECTION_STATE_CHANGED: - s.client.sendGlobalMessage("Upstream: " + hubEvent.StateChange.Format()) + s.sendClientGlobalMessage("Upstream: " + hubEvent.StateChange.Format()) case libnmdc.EVENT_HUBNAME_CHANGED: - s.client.reply(rplTopic, BLESSED_CHANNEL, hubEvent.Nick) + s.reply(rplTopic, BLESSED_CHANNEL, hubEvent.Nick) // c.reply(rplNoTopic, BLESSED_CHANNEL) case libnmdc.EVENT_PRIVATE: - s.client.reply(rplMsg, s.client.nick, hubEvent.Nick, hubEvent.Message) + s.reply(rplMsg, s.clientNick, hubEvent.Nick, hubEvent.Message) case libnmdc.EVENT_PUBLIC: - s.client.reply(rplMsg, s.client.nick, BLESSED_CHANNEL, hubEvent.Message) + s.reply(rplMsg, s.clientNick, BLESSED_CHANNEL, hubEvent.Message) case libnmdc.EVENT_SYSTEM_MESSAGE_FROM_CONN, libnmdc.EVENT_SYSTEM_MESSAGE_FROM_HUB: - s.client.sendGlobalMessage(hubEvent.Message) + s.sendClientGlobalMessage(hubEvent.Message) } } @@ -143,26 +137,26 @@ func (s *Server) handleCommand(command string, args []string) { switch command { case "PING": - s.client.reply(rplPong) + s.reply(rplPong) case "INFO": - s.client.reply(rplInfo, APP_DESCRIPTION) + s.reply(rplInfo, APP_DESCRIPTION) case "VERSION": - s.client.reply(rplVersion, VERSION) + s.reply(rplVersion, VERSION) case "PASS": // RFC2812 registration // Stash the password for later if len(args) < 1 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } s.upstreamLauncher.NickPassword = args[0] case "NICK": if len(args) < 1 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } @@ -170,43 +164,43 @@ func (s *Server) handleCommand(command string, args []string) { // allow set, as part of the login phase s.upstreamLauncher.Self.Nick = args[0] } else { - s.client.reply(rplKill, "Can't change nicks on this server", "") - s.client.disconnect() + s.reply(rplKill, "Can't change nicks on this server", "") + s.DisconnectClient() } case "USER": // This command sets altname, realname, ... none of which we use // It's the final step in a PASS/NICK/USER login handshake. - if s.client.registered == true { - s.client.reply(rplKill, "You're already registered.", "") - s.client.disconnect() + if s.clientRegistered == true { + s.reply(rplKill, "You're already registered.", "") + s.DisconnectClient() return } - if s.client.nick == "" { - s.client.reply(rplKill, "Your nickname is already being used", "") - s.client.disconnect() + if s.clientNick == "" { + s.reply(rplKill, "Your nickname is already being used", "") + s.DisconnectClient() return } - s.client.reply(rplWelcome) - s.client.registered = true + s.reply(rplWelcome) + s.clientRegistered = true // Spawn upstream connection - go s.ProtocolReadLoop_NMDC(nil) // FIXME need shutdown synchronisation + go s.upstreamWorker() // FIXME need shutdown synchronisation // Tell the user that they themselves joined the chat channel - s.client.reply(rplJoin, s.client.nick, BLESSED_CHANNEL) + s.reply(rplJoin, s.clientNick, BLESSED_CHANNEL) case "JOIN": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } if len(args) < 1 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } @@ -216,27 +210,27 @@ func (s *Server) handleCommand(command string, args []string) { case "0": // Intend to quit all channels, but we don't allow that default: - s.client.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.", "") - s.client.disconnect() + s.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.", "") + s.DisconnectClient() } case "PART": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } // you can check out any time you like, but you can never leave - // we'll need to transmit s.client.reply(rplPart, c.nick, channel.name, reason) messages on behalf of other nmdc users, though + // we'll need to transmit s.reply(rplPart, s.clientNick, channel.name, reason) messages on behalf of other nmdc users, though case "PRIVMSG": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } if len(args) < 2 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } @@ -252,114 +246,218 @@ func (s *Server) handleCommand(command string, args []string) { s.upstream.SayPrivate(args[0], message) } else { - s.client.reply(errNoSuchNick, args[0]) + s.reply(errNoSuchNick, args[0]) } case "QUIT": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } - s.client.disconnect() + s.DisconnectClient() case "TOPIC": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } if len(args) < 1 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } exists := strings.ToLower(args[0]) == BLESSED_CHANNEL if exists == false { - s.client.reply(errNoSuchNick, args[0]) + s.reply(errNoSuchNick, args[0]) return } // Valid topic get if len(args) == 1 { - s.client.reply(rplTopic, BLESSED_CHANNEL, s.upstream.HubName) + s.reply(rplTopic, BLESSED_CHANNEL, s.upstream.HubName) return } // Disallow topic set - s.client.reply(errNoPriv) + s.reply(errNoPriv) return case "LIST": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } - listItem := fmt.Sprintf("%s %d :%s", BLESSED_CHANNEL, len(s.channel.clientMap), s.upstream.HubName) - s.client.reply(rplList, listItem) - s.client.reply(rplListEnd) + listItem := fmt.Sprintf("%s %d :%s", BLESSED_CHANNEL, len(s.upstream.Users), s.upstream.HubName) + s.reply(rplList, listItem) + s.reply(rplListEnd) case "OPER": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } if len(args) < 2 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } // Can't use this command. - s.client.reply(errPassword) + s.reply(errPassword) case "KILL": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } - s.client.reply(errNoPriv) + s.reply(errNoPriv) return case "KICK": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } - s.client.reply(errNoPriv) + s.reply(errNoPriv) return case "MODE": - if s.client.registered == false { - s.client.reply(errNotReg) + if s.clientRegistered == false { + s.reply(errNotReg) return } if len(args) < 1 { - s.client.reply(errMoreArgs) + s.reply(errMoreArgs) return } if strings.ToLower(args[0]) != BLESSED_CHANNEL { - s.client.reply(errNoSuchNick, args[0]) + s.reply(errNoSuchNick, args[0]) return } if len(args) == 1 { // No more args, they just want the mode - s.client.reply(rplChannelModeIs, args[0], BLESSED_CHANNEL_MODE, "") + s.reply(rplChannelModeIs, args[0], BLESSED_CHANNEL_MODE, "") } else { // Setting modes is disallowed - s.client.reply(errNoPriv) + s.reply(errNoPriv) } return default: - s.client.reply(errUnknownCommand, command) + s.reply(errUnknownCommand, command) + } +} + +func (s *Server) DisconnectClient() { + s.clientConn.Close() + s.clientConn = nil +} + +func (s *Server) sendClientGlobalMessage(motd string) { + s.reply(rplMOTDStart) + + for len(motd) > 80 { + s.reply(rplMOTD, motd[:80]) + motd = motd[80:] + } + if len(motd) > 0 { + s.reply(rplMOTD, motd) + } + + s.reply(rplEndOfMOTD) +} + +// Send a reply to a user with the code specified +func (s *Server) reply(code replyCode, args ...string) { + + switch code { + case rplWelcome: + s.writeClient(fmt.Sprintf(":%s 001 %s :Welcome to %s", s.name, s.clientNick, s.name)) + case rplJoin: + s.writeClient(fmt.Sprintf(":%s JOIN %s", args[0], args[1])) + case rplPart: + s.writeClient(fmt.Sprintf(":%s PART %s %s", args[0], args[1], args[2])) + case rplTopic: + s.writeClient(fmt.Sprintf(":%s 332 %s %s :%s", s.name, s.clientNick, args[0], args[1])) + case rplNoTopic: + s.writeClient(fmt.Sprintf(":%s 331 %s %s :No topic is set", s.name, s.clientNick, args[0])) + case rplNames: + s.writeClient(fmt.Sprintf(":%s 353 %s = %s :%s", s.name, s.clientNick, args[0], args[1])) + case rplEndOfNames: + s.writeClient(fmt.Sprintf(":%s 366 %s %s :End of NAMES list", s.name, s.clientNick, args[0])) + case rplNickChange: + s.writeClient(fmt.Sprintf(":%s NICK %s", args[0], args[1])) + case rplKill: + s.writeClient(fmt.Sprintf(":%s KILL %s A %s", args[0], s.clientNick, args[1])) + case rplMsg: + for _, itm := range strings.Split(args[2], "\n") { + s.writeClient(fmt.Sprintf(":%s PRIVMSG %s %s", args[0], args[1], itm)) + } + case rplList: + s.writeClient(fmt.Sprintf(":%s 322 %s %s", s.name, s.clientNick, args[0])) + case rplListEnd: + s.writeClient(fmt.Sprintf(":%s 323 %s", s.name, s.clientNick)) + case rplOper: + s.writeClient(fmt.Sprintf(":%s 381 %s :You are now an operator", s.name, s.clientNick)) + case rplChannelModeIs: + s.writeClient(fmt.Sprintf(":%s 324 %s %s %s %s", s.name, s.clientNick, args[0], args[1], args[2])) + case rplKick: + s.writeClient(fmt.Sprintf(":%s KICK %s %s %s", args[0], args[1], args[2], args[3])) + case rplInfo: + s.writeClient(fmt.Sprintf(":%s 371 %s :%s", s.name, s.clientNick, args[0])) + case rplVersion: + s.writeClient(fmt.Sprintf(":%s 351 %s %s", s.name, s.clientNick, args[0])) + case rplMOTDStart: + s.writeClient(fmt.Sprintf(":%s 375 %s :- Message of the day - ", s.name, s.clientNick)) + case rplMOTD: + s.writeClient(fmt.Sprintf(":%s 372 %s :- %s", s.name, s.clientNick, args[0])) + case rplEndOfMOTD: + s.writeClient(fmt.Sprintf(":%s 376 %s :End of MOTD Command", s.name, s.clientNick)) + case rplPong: + s.writeClient(fmt.Sprintf(":%s PONG %s %s", s.name, s.clientNick, s.name)) + case errMoreArgs: + s.writeClient(fmt.Sprintf(":%s 461 %s :Not enough params", s.name, s.clientNick)) + case errNoNick: + s.writeClient(fmt.Sprintf(":%s 431 %s :No nickname given", s.name, s.clientNick)) + case errInvalidNick: + s.writeClient(fmt.Sprintf(":%s 432 %s %s :Erronenous nickname", s.name, s.clientNick, args[0])) + case errNickInUse: + s.writeClient(fmt.Sprintf(":%s 433 %s %s :Nick already in use", s.name, s.clientNick, args[0])) + case errAlreadyReg: + s.writeClient(fmt.Sprintf(":%s 462 :You need a valid nick first", s.name)) + case errNoSuchNick: + s.writeClient(fmt.Sprintf(":%s 401 %s %s :No such nick/channel", s.name, s.clientNick, args[0])) + case errUnknownCommand: + s.writeClient(fmt.Sprintf(":%s 421 %s %s :Unknown command", s.name, s.clientNick, args[0])) + case errNotReg: + s.writeClient(fmt.Sprintf(":%s 451 :You have not registered", s.name)) + case errPassword: + s.writeClient(fmt.Sprintf(":%s 464 %s :Error, password incorrect", s.name, s.clientNick)) + case errNoPriv: + s.writeClient(fmt.Sprintf(":%s 481 %s :Permission denied", s.name, s.clientNick)) + case errCannotSend: + s.writeClient(fmt.Sprintf(":%s 404 %s %s :Cannot send to channel", s.name, s.clientNick, args[0])) + } +} + +func (s *Server) writeClient(output string) { + if s.clientConn == nil { + return + } + + s.clientConn.SetWriteDeadline(time.Now().Add(time.Second * 30)) + if _, err := fmt.Fprintf(s.clientConn, "%s\r\n", output); err != nil { + s.DisconnectClient() + return } } diff --git a/typedefs.go b/typedefs.go index 46e9d3c..4a59c6e 100644 --- a/typedefs.go +++ b/typedefs.go @@ -20,24 +20,6 @@ var ( channelRegexp = regexp.MustCompile(`^#[a-zA-Z0-9_\-]+$`) ) -type eventType int - -const ( - disconnected eventType = iota - command -) - -type Event struct { - client *Client - input string - event eventType -} - -type Channel struct { - clientMap map[string]*Client - modeMap map[string]*ClientMode -} - type ClientMode struct { operator bool //Channel operator }