// 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 Send(*irc.Message) Receive(*irc.Message) Register() chan bool Destroy() } type RemoteClient struct { server *Server name string password string modes string isSSL bool isRegistered bool isAuthed bool isClosed bool receive chan *irc.Message register chan bool conn net.Conn writeq chan string } func NewRemoteClient(srv *Server, conn net.Conn) *RemoteClient { cl := new(RemoteClient) cl.server = srv cl.name = "" cl.password = "" cl.modes = "" cl.receive = make(chan *irc.Message) cl.register = 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.dispatch() xlog.Info("RemoteClient connected") return cl } func (cl *RemoteClient) Name() string { return cl.name } 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) Register() chan bool { return cl.register } 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) dispatch() { for { time.Sleep(1 * time.Millisecond) if cl.isClosed { return } select { case msg := <-cl.receive: cl.writeMsg(msg) 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.handleLine(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...) } var lineFuncs = map[string]func(*RemoteClient, *irc.Message) bool{ "PASS": handleLinePass, "NICK": handleLineNick, "USER": handleLineUser, } func (cl *RemoteClient) handleLine(s string) { xlog.Debug("handleLine: [%s] '%s'", cl.name, s) msg := irc.Parse(s) hook, exists := lineFuncs[msg.Cmd] forward := true if exists { forward = hook(cl, msg) } if forward { cl.Send(msg) } } func checkAuth(cl *RemoteClient) { if cl.name == "" || cl.password == "" { return } } func handleLinePass(cl *RemoteClient, msg *irc.Message) bool { cl.password = msg.Args[0] return false } func handleLineNick(cl *RemoteClient, msg *irc.Message) bool { if cl.name != "" { return false } if len(msg.Args) < 1 { xlog.Warning("Nicksalat!") return false } cl.name = msg.Args[0] cl.server.AddClient <- cl if registered := <-cl.register; registered { cl.isRegistered = true xlog.Info("User '%s' registered", msg.Args[0]) } else { xlog.Error("User '%s' already connected", msg.Args[0]) cl.server.DelClient <- cl } return false } func handleLineUser(cl *RemoteClient, msg *irc.Message) bool { return false }