// vim:ts=4:sts=4:sw=4:noet:tw=72 package ircd import ( "bufio" "fmt" "io" "net" "strings" "time" "code.dnix.de/an/irc" "code.dnix.de/an/xlog" ) type Client interface { Name() string Register(bool) Send(*irc.Message) Receive(*irc.Message) Destroy() } type RemoteClient struct { Registered chan bool server *Server name string password string modes string isSSL bool isRegistered bool isAuthed bool isClosed bool receive chan *irc.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 *irc.Message) cl.registered = make(chan bool) cl.isRegistered = false cl.isAuthed = false cl.isClosed = false cl.conn = conn cl.writeq = make(chan string, 256) 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) Register(success bool) { cl.registered <- success } func (cl *RemoteClient) Send(msg *irc.Message) { msg.Pre = cl.name cl.server.Dispatch(msg) } func (cl *RemoteClient) Receive(msg *irc.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(1 * 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) connReader() { input := bufio.NewReader(cl.conn) for { if cl.isClosed { xlog.Debug("connReader: exiting") return } s, err := input.ReadString('\n') if err == io.EOF { xlog.Info("connReader: Connection closed by peer") cl.server.DelClient(cl) return } if err != nil { xlog.Error("connReader: %s", err.Error()) cl.server.DelClient(cl) return } s = strings.Trim(s, "\r\n") cl.handleCmd(s) } } func (cl *RemoteClient) connWriter() { for line := range cl.writeq { written := 0 bytes := []byte(line + "\r\n") for written < len(line) { 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 *irc.Message) { 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 := irc.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 } }