package main import ( "bytes" "fmt" "io" "libnmdc" "net" "strings" "time" ) type Server struct { eventChan chan Event running bool name string client *Client channel Channel // Single blessed channel upstreamAddr libnmdc.HubAddress upstream libnmdc.HubConnection motd string } func NewServer(name string, upstream libnmdc.HubAddress) *Server { return &Server{eventChan: make(chan Event), name: name, client: nil, motd: "Connected to " + name, upstreamAddr: upstream, 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, } // 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) handleCommandEvent(input string) { //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) { switch command { case "PING": client.reply(rplPong) case "INFO": client.reply(rplInfo, APP_DESCRIPTION) case "VERSION": client.reply(rplVersion, VERSION) case "NICK": client.reply(rplKill, "Can't change nicks on this server", "") client.disconnect() case "USER": if client.registered == true { client.reply(rplKill, "You're already registered.", "") client.disconnect() } if client.nick == "" { client.reply(rplKill, "Your nickname is already being used", "") client.disconnect() } else { client.reply(rplWelcome) client.registered = true // Spawn } case "JOIN": if client.registered == false { client.reply(errNotReg) return } if len(args) < 1 { client.reply(errMoreArgs) return } switch args[0] { case BLESSED_CHANNEL: // That's fine, but they're already there case "0": // Intend to quit all channels, but we don't allow that default: client.reply(rplKill, "There is only '"+BLESSED_CHANNEL+"'.", "") client.disconnect() } case "PART": if client.registered == false { client.reply(errNotReg) return } // you can check out any time you like, but you can never leave // we'll need to transmit client.reply(rplPart, c.nick, channel.name, reason) messages on behalf of other nmdc users, though case "PRIVMSG": if client.registered == false { client.reply(errNotReg) return } if len(args) < 2 { client.reply(errMoreArgs) return } message := strings.Join(args[1:], " ") // IRC is case-insensitive case-preserving. We can respect that for the // channel name, but not really for user nicks recipient := strings.ToLower(args[0]) if recipient == BLESSED_CHANNEL { for _, c := range s.channel.clientMap { if c != client { c.reply(rplMsg, client.nick, args[0], message) } } } else if nmdcUser, clientExists := s.upstream.Users[args[0]]; clientExists { s.upstream.SayPrivate(recipient, message) // client2.reply(rplMsg, client.nick, client2.nick, message) } else { client.reply(errNoSuchNick, args[0]) } case "QUIT": if client.registered == false { client.reply(errNotReg) return } client.disconnect() case "TOPIC": if client.registered == false { client.reply(errNotReg) return } if len(args) < 1 { client.reply(errMoreArgs) return } exists := strings.ToLower(args[0]) == BLESSED_CHANNEL if exists == false { client.reply(errNoSuchNick, args[0]) return } // Valid topic get if len(args) == 1 { client.reply(rplTopic, BLESSED_CHANNEL, s.upstream.HubName) return } // Disallow topic set client.reply(errNoPriv) return case "LIST": if client.registered == false { client.reply(errNotReg) return } listItem := fmt.Sprintf("%s %d :%s", BLESSED_CHANNEL, len(s.channel.clientMap), s.upstream.HubName) client.reply(rplList, listItem) client.reply(rplListEnd) case "OPER": if client.registered == false { client.reply(errNotReg) return } if len(args) < 2 { client.reply(errMoreArgs) return } //username := args[0] //password := args[1] if false { // op the user client.operator = true client.reply(rplOper) return } else { client.reply(errPassword) } case "KILL": if client.registered == false { client.reply(errNotReg) return } client.reply(errNoPriv) return case "KICK": if client.registered == false { client.reply(errNotReg) return } client.reply(errNoPriv) return case "MODE": if client.registered == false { client.reply(errNotReg) return } if len(args) < 1 { client.reply(errMoreArgs) return } if strings.ToLower(args[0]) != BLESSED_CHANNEL { client.reply(errNoSuchNick, args[0]) return } if len(args) == 1 { //No more args, they just want the mode client.reply(rplChannelModeIs, args[0], BLESSED_CHANNEL_MODE, "") } else { // Setting modes is disallowed client.reply(errNoPriv) } return default: client.reply(errUnknownCommand, command) } }