// vim:ts=4:sts=4:sw=4:noet:tw=72 package irc import ( "bytes" "fmt" "io" "net" "strings" "time" "git.dnix.de/an/xlog" ) type Client interface { Name() string Password() string Register(bool) Send(*Message) Receive(*Message) Destroy() } type RemoteClient struct { server *Server name string password string modes string isSSL bool isRegistered bool isAuthed bool isClosed bool receive chan *Message registered chan bool conn net.Conn writeq chan string } func NewRemoteClient(sv *Server, conn net.Conn) *RemoteClient { cl := new(RemoteClient) cl.server = sv cl.name = "" cl.password = "" cl.modes = "" cl.receive = make(chan *Message, 1024) cl.registered = make(chan bool) cl.isRegistered = false cl.isAuthed = false cl.isClosed = false cl.conn = conn cl.writeq = make(chan string, 1024) go cl.connReader() go cl.connWriter() go cl.dispatcher() xlog.Info("RemoteClient connected") return cl } func (cl *RemoteClient) Name() string { return cl.name } func (cl *RemoteClient) Password() string { return cl.password } func (cl *RemoteClient) Register(success bool) { cl.registered <- success } func (cl *RemoteClient) Send(msg *Message) { msg.Pre = cl.name cl.server.Dispatch(msg) } func (cl *RemoteClient) Receive(msg *Message) { cl.receive <- msg } func (cl *RemoteClient) AddMode(mode string) { cl.modes = cl.modes + mode } func (cl *RemoteClient) DelMode(mode string) { cl.modes = strings.Replace(cl.modes, mode, "", -1) } func (cl *RemoteClient) HasMode(mode string) bool { return strings.IndexRune(cl.modes, rune(mode[0])) != -1 } func (cl *RemoteClient) Destroy() { if cl.isClosed { return } cl.isClosed = true close(cl.writeq) cl.conn.Write([]byte("ERROR :Closing link")) cl.conn.Close() if cl.name != "" { xlog.Info("RemoteClient '%s' disconnected", cl.name) } else { xlog.Info("RemoteClient disconnected") } } func (cl *RemoteClient) dispatcher() { for { time.Sleep(10 * time.Millisecond) if cl.isClosed { return } select { case msg := <-cl.receive: cl.writeMsg(msg) case success := <-cl.registered: if !success { cl.name = "" cl.Destroy() xlog.Debug("User registration failed: '%s'", cl.name) return } xlog.Debug("User '%s' registered", cl.name) default: continue } } } /*func (cl *RemoteClient) pinger() { for { time.Sleep(30 * time.Second) if cl.isClosed { return } cl.writeLine("PING %s", cl.server.host) } }*/ func (cl *RemoteClient) connReader() { for { time.Sleep(10 * time.Millisecond) buf := make([]byte, 4096) if cl.isClosed { xlog.Debug("connReader: exiting") return } cl.conn.SetReadDeadline(time.Now().Add(time.Second * 5)) n, err := cl.conn.Read(buf) if err == io.EOF { xlog.Info("connReader: Connection closed by peer") cl.server.DelClient(cl) return } if err != nil { continue } raw := buf[:n] raw = bytes.Replace(raw, []byte("\r\n"), []byte("\n"), -1) raw = bytes.Replace(raw, []byte("\r"), []byte("\n"), -1) lines := bytes.Split(raw, []byte("\n")) for _, line := range lines { if len(line) > 0 { cl.handleCmd(string(line)) } } } } func (cl *RemoteClient) connWriter() { for line := range cl.writeq { time.Sleep(10 * time.Millisecond) if cl.isClosed { return } written := 0 bytes := []byte(line + "\r\n") for written < len(line) { cl.conn.SetWriteDeadline(time.Now().Add(time.Second * 5)) n, err := cl.conn.Write(bytes[written:]) if err == io.EOF { xlog.Info("connWriter: Connection closed by peer") cl.server.DelClient(cl) return } else if err != nil { xlog.Error("connWriter: %s", err.Error()) cl.server.DelClient(cl) return } written += n } } xlog.Debug("connWriter: exiting") } func (cl *RemoteClient) writeMsg(msg *Message) { clid := strings.ToLower(msg.Pre) if _, exists := cl.server.clients[clid]; exists { msg.Pre = msg.Pre + "!" + msg.Pre + "@" + cl.server.clHosts[clid] } cl.writeLine(msg.String()) } func (cl *RemoteClient) writeLine(format string, a ...interface{}) { cl.writeq <- fmt.Sprintf(format, a...) } func (cl *RemoteClient) handleCmd(s string) { msg := Parse(s) msg.Cmd = strings.ToUpper(msg.Cmd) if cl.name != "" { cl.Send(msg) return } // If client name is not set, intercept cmds until we get one switch msg.Cmd { case "PASS": cl.password = msg.Args[0] case "NICK": cl.name = msg.Args[0] cl.server.AddClient(cl) case "USER": } } func checkAuth(cl *RemoteClient) { if cl.name == "" || cl.password == "" { return } }