diff --git a/client.go b/client.go index 43d7535..1d4707e 100644 --- a/client.go +++ b/client.go @@ -1,21 +1,15 @@ package main import ( - "bytes" "fmt" - "io" "net" "strings" "time" ) type Client struct { - server *Server connection net.Conn - signalChan chan signalCode - outputChan chan string nick string - key string registered bool connected bool operator bool @@ -30,7 +24,7 @@ func (c *Client) joinChannel(channelName string) { } channel := c.server.channel - if _, inChannel := channel.clientMap[c.key]; inChannel { + if _, inChannel := channel.clientMap[c.nick]; inChannel { //Client is already in the channel, do nothing return } @@ -53,7 +47,7 @@ func (c *Client) joinChannel(channelName string) { for _, client := range channel.clientMap { prefix := "" - if mode, exists := channel.modeMap[client.key]; exists { + if mode, exists := channel.modeMap[client.nick]; exists { prefix = mode.Prefix() } @@ -74,7 +68,6 @@ func (c *Client) joinChannel(channelName string) { func (c *Client) disconnect() { c.connected = false - c.signalChan <- signalStop } //Send a reply to a user with the code specified @@ -85,153 +78,76 @@ func (c *Client) reply(code replyCode, args ...string) { switch code { case rplWelcome: - c.outputChan <- fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name) + c.write(fmt.Sprintf(":%s 001 %s :Welcome to %s", c.server.name, c.nick, c.server.name)) case rplJoin: - c.outputChan <- fmt.Sprintf(":%s JOIN %s", args[0], args[1]) + c.write(fmt.Sprintf(":%s JOIN %s", args[0], args[1])) case rplPart: - c.outputChan <- fmt.Sprintf(":%s PART %s %s", args[0], args[1], args[2]) + c.write(fmt.Sprintf(":%s PART %s %s", args[0], args[1], args[2])) case rplTopic: - c.outputChan <- fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1]) + c.write(fmt.Sprintf(":%s 332 %s %s :%s", c.server.name, c.nick, args[0], args[1])) case rplNoTopic: - c.outputChan <- fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 331 %s %s :No topic is set", c.server.name, c.nick, args[0])) case rplNames: - c.outputChan <- fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1]) + c.write(fmt.Sprintf(":%s 353 %s = %s :%s", c.server.name, c.nick, args[0], args[1])) case rplEndOfNames: - c.outputChan <- fmt.Sprintf(":%s 366 %s %s :End of NAMES list", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 366 %s %s :End of NAMES list", c.server.name, c.nick, args[0])) case rplNickChange: - c.outputChan <- fmt.Sprintf(":%s NICK %s", args[0], args[1]) + c.write(fmt.Sprintf(":%s NICK %s", args[0], args[1])) case rplKill: - c.outputChan <- fmt.Sprintf(":%s KILL %s A %s", args[0], c.nick, args[1]) + c.write(fmt.Sprintf(":%s KILL %s A %s", args[0], c.nick, args[1])) case rplMsg: - c.outputChan <- fmt.Sprintf(":%s PRIVMSG %s %s", args[0], args[1], args[2]) + c.write(fmt.Sprintf(":%s PRIVMSG %s %s", args[0], args[1], args[2])) case rplList: - c.outputChan <- fmt.Sprintf(":%s 322 %s %s", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 322 %s %s", c.server.name, c.nick, args[0])) case rplListEnd: - c.outputChan <- fmt.Sprintf(":%s 323 %s", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 323 %s", c.server.name, c.nick)) case rplOper: - c.outputChan <- fmt.Sprintf(":%s 381 %s :You are now an operator", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 381 %s :You are now an operator", c.server.name, c.nick)) case rplChannelModeIs: - c.outputChan <- fmt.Sprintf(":%s 324 %s %s %s %s", c.server.name, c.nick, args[0], args[1], args[2]) + c.write(fmt.Sprintf(":%s 324 %s %s %s %s", c.server.name, c.nick, args[0], args[1], args[2])) case rplKick: - c.outputChan <- fmt.Sprintf(":%s KICK %s %s %s", args[0], args[1], args[2], args[3]) + c.write(fmt.Sprintf(":%s KICK %s %s %s", args[0], args[1], args[2], args[3])) case rplInfo: - c.outputChan <- fmt.Sprintf(":%s 371 %s :%s", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 371 %s :%s", c.server.name, c.nick, args[0])) case rplVersion: - c.outputChan <- fmt.Sprintf(":%s 351 %s %s", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 351 %s %s", c.server.name, c.nick, args[0])) case rplMOTDStart: - c.outputChan <- fmt.Sprintf(":%s 375 %s :- Message of the day - ", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 375 %s :- Message of the day - ", c.server.name, c.nick)) case rplMOTD: - c.outputChan <- fmt.Sprintf(":%s 372 %s :- %s", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 372 %s :- %s", c.server.name, c.nick, args[0])) case rplEndOfMOTD: - c.outputChan <- fmt.Sprintf(":%s 376 %s :End of MOTD Command", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 376 %s :End of MOTD Command", c.server.name, c.nick)) case rplPong: - c.outputChan <- fmt.Sprintf(":%s PONG %s %s", c.server.name, c.nick, c.server.name) + c.write(fmt.Sprintf(":%s PONG %s %s", c.server.name, c.nick, c.server.name)) case errMoreArgs: - c.outputChan <- fmt.Sprintf(":%s 461 %s :Not enough params", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 461 %s :Not enough params", c.server.name, c.nick)) case errNoNick: - c.outputChan <- fmt.Sprintf(":%s 431 %s :No nickname given", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 431 %s :No nickname given", c.server.name, c.nick)) case errInvalidNick: - c.outputChan <- fmt.Sprintf(":%s 432 %s %s :Erronenous nickname", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 432 %s %s :Erronenous nickname", c.server.name, c.nick, args[0])) case errNickInUse: - c.outputChan <- fmt.Sprintf(":%s 433 %s %s :Nick already in use", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 433 %s %s :Nick already in use", c.server.name, c.nick, args[0])) case errAlreadyReg: - c.outputChan <- fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name) + c.write(fmt.Sprintf(":%s 462 :You need a valid nick first", c.server.name)) case errNoSuchNick: - c.outputChan <- fmt.Sprintf(":%s 401 %s %s :No such nick/channel", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 401 %s %s :No such nick/channel", c.server.name, c.nick, args[0])) case errUnknownCommand: - c.outputChan <- fmt.Sprintf(":%s 421 %s %s :Unknown command", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 421 %s %s :Unknown command", c.server.name, c.nick, args[0])) case errNotReg: - c.outputChan <- fmt.Sprintf(":%s 451 :You have not registered", c.server.name) + c.write(fmt.Sprintf(":%s 451 :You have not registered", c.server.name)) case errPassword: - c.outputChan <- fmt.Sprintf(":%s 464 %s :Error, password incorrect", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 464 %s :Error, password incorrect", c.server.name, c.nick)) case errNoPriv: - c.outputChan <- fmt.Sprintf(":%s 481 %s :Permission denied", c.server.name, c.nick) + c.write(fmt.Sprintf(":%s 481 %s :Permission denied", c.server.name, c.nick)) case errCannotSend: - c.outputChan <- fmt.Sprintf(":%s 404 %s %s :Cannot send to channel", c.server.name, c.nick, args[0]) + c.write(fmt.Sprintf(":%s 404 %s %s :Cannot send to channel", c.server.name, c.nick, args[0])) } } -func (c *Client) clientThread() { - readSignalChan := make(chan signalCode, 3) - writeSignalChan := make(chan signalCode, 3) - writeChan := make(chan string, 100) - - c.server.eventChan <- Event{client: c, event: connected} - - go c.readThread(readSignalChan) - go c.writeThread(writeSignalChan, writeChan) - - defer func() { - // Implicit part from all channels - // FIXME also drop the upstream connection - c.connection.Close() - }() - - for { - select { - case signal := <-c.signalChan: - if signal == signalStop { - readSignalChan <- signalStop - writeSignalChan <- signalStop - return - } - case line := <-c.outputChan: - select { - case writeChan <- line: - continue - default: - c.disconnect() - } - } - } - -} - -func (c *Client) readThread(signalChan chan signalCode) { - for { - select { - case signal := <-signalChan: - if signal == signalStop { - return - } - default: - c.connection.SetReadDeadline(time.Now().Add(time.Second * 3)) - buf := make([]byte, 512) - ln, err := c.connection.Read(buf) - if err != nil { - if err == io.EOF { - c.disconnect() - return - } - continue - } - - rawLines := buf[:ln] - rawLines = bytes.Replace(rawLines, []byte("\r\n"), []byte("\n"), -1) - rawLines = bytes.Replace(rawLines, []byte("\r"), []byte("\n"), -1) - lines := bytes.Split(rawLines, []byte("\n")) - for _, line := range lines { - if len(line) > 0 { - c.server.eventChan <- Event{client: c, event: command, input: string(line)} - } - } - } - } -} - -func (c *Client) writeThread(signalChan chan signalCode, outputChan chan string) { - for { - select { - case signal := <-signalChan: - if signal == signalStop { - return - } - case output := <-outputChan: - c.connection.SetWriteDeadline(time.Now().Add(time.Second * 30)) - if _, err := fmt.Fprintf(c.connection, "%s\r\n", output); err != nil { - c.disconnect() - return - } - } +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 39ef6cd..cedbbdb 100644 --- a/main.go +++ b/main.go @@ -16,10 +16,6 @@ var ( func main() { flag.Parse() - if len(*serverName) == 0 { - log.Println("Please specify the -servername parameter.") - return - } listener, err := net.Listen("tcp", *ircAddress) if err != nil { @@ -41,7 +37,6 @@ func main() { // from the irc client server := NewServer(*serverName, libnmdc.HubAddress(*dcAddress)) - go server.Run() - server.HandleConnection(conn) + go server.RunClient(conn) } } diff --git a/server.go b/server.go index 412a070..dbefefd 100644 --- a/server.go +++ b/server.go @@ -1,10 +1,13 @@ package main import ( + "bytes" "fmt" + "io" "libnmdc" "net" "strings" + "time" ) type Server struct { @@ -31,57 +34,67 @@ func NewServer(name string, upstream libnmdc.HubAddress) *Server { } } -func (s *Server) Run() { - for event := range s.eventChan { - s.handleEvent(event) - } -} +func (s *Server) RunClient(conn net.Conn) { -func (s *Server) HandleConnection(conn net.Conn) { - client := &Client{server: s, + s.client = &Client{ connection: conn, - outputChan: make(chan string), - signalChan: make(chan signalCode, 3), connected: true, } - go client.clientThread() + // Send the connection handshake + s.client.reply(rplMOTDStart) + motd := s.motd + for len(motd) > 80 { + s.client.reply(rplMOTD, motd[:80]) + motd = motd[80:] + } + if len(motd) > 0 { + s.client.reply(rplMOTD, motd) + } + s.client.reply(rplEndOfMOTD) + + // Read loop + for { + s.client.connection.SetReadDeadline(time.Now().Add(time.Second * CLIENT_READ_TIMEOUT_SEC)) + buf := make([]byte, CLIENT_READ_BUFFSIZE) + ln, err := s.client.connection.Read(buf) + if err != nil { + if err == io.EOF { + s.client.disconnect() + return // FIXME cleanup + } + continue + } + + rawLines := buf[:ln] + rawLines = bytes.Replace(rawLines, []byte("\r\n"), []byte("\n"), -1) + rawLines = bytes.Replace(rawLines, []byte("\r"), []byte("\n"), -1) + lines := bytes.Split(rawLines, []byte("\n")) + for _, line := range lines { + if len(line) > 0 { + s.handleCommandEvent(string(line)) + } + } + } + } -func (s *Server) handleEvent(e Event) { +func (s *Server) handleCommandEvent(input string) { - switch e.event { - case connected: - //Client connected - e.client.reply(rplMOTDStart) - motd := s.motd - for len(motd) > 80 { - e.client.reply(rplMOTD, motd[:80]) - motd = motd[80:] - } - if len(motd) > 0 { - e.client.reply(rplMOTD, motd) - } - e.client.reply(rplEndOfMOTD) - - case disconnected: - //Client disconnected - - case command: - //Client send a command - fields := strings.Fields(e.input) - if len(fields) < 1 { - return - } - - if strings.HasPrefix(fields[0], ":") { - fields = fields[1:] - } - command := strings.ToUpper(fields[0]) - args := fields[1:] - - s.handleCommand(e.client, command, args) + //Client send a command + fields := strings.Fields(input) + if len(fields) < 1 { + return } + + if strings.HasPrefix(fields[0], ":") { + fields = fields[1:] + } + command := strings.ToUpper(fields[0]) + args := fields[1:] + + s.handleCommand(s.client, command, args) + } func (s *Server) handleCommand(client *Client, command string, args []string) { diff --git a/typedefs.go b/typedefs.go index 28b925d..46e9d3c 100644 --- a/typedefs.go +++ b/typedefs.go @@ -10,7 +10,9 @@ const ( BLESSED_CHANNEL = "#chat" // must be lowercase BLESSED_CHANNEL_MODE = "n" // means that you have to be in the channel to chat, but that's it - NICKS_PER_PROTOMSG = 128 //The capacity sets the max number of nicks to send per message + NICKS_PER_PROTOMSG = 128 //The capacity sets the max number of nicks to send per message + CLIENT_READ_BUFFSIZE = 512 + CLIENT_READ_TIMEOUT_SEC = 3 ) var ( @@ -21,8 +23,7 @@ var ( type eventType int const ( - connected eventType = iota - disconnected + disconnected eventType = iota command )