package ircd import ( "bufio" "code.dnix.de/xlog" "fmt" "io" "net" "strings" "time" ) type Client interface { Name() string Send(*Message) Receive(*Message) Register() chan bool AddMode(string) DelMode(string) HasMode(string) bool } type RemoteClient struct { server *Server name string password string modes string isSSL bool isRegistered bool isAuthed bool isClosed bool receive chan *Message register chan bool conn net.Conn writeq chan string channels map[*Channel]bool } func NewRemoteClient(srv *Server, conn net.Conn) *RemoteClient { cl := new(RemoteClient) cl.server = srv cl.name = "" cl.password = "" cl.modes = "" cl.receive = make(chan *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.loop() xlog.Info("Client connected.") return cl } func (cl *RemoteClient) Name() string { return cl.name } func (cl *RemoteClient) Send(msg *Message) { cl.server.Receive <- msg } func (cl *RemoteClient) Receive(msg *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) writeMsg(msg *Message) { var src, ctx, cmd, args, text string src = fmt.Sprintf("%s!%s@%s", msg.Src, msg.Src, cl.server.Host) ctx = msg.Ctx cmd = msg.Cmd text = msg.Text args = "" for _, arg := range msg.Args { args += " " + arg } if text != "" { cl.writeLine(fmt.Sprintf(":%s %s %s%s :%s", src, cmd, ctx, args, text)) } else { cl.writeLine(fmt.Sprintf(":%s %s %s%s", src, cmd, ctx, args)) } } func (cl *RemoteClient) loop() { for { time.Sleep(1 * time.Millisecond) if cl.isClosed { return } select { case msg := <-cl.receive: cl.writeMsg(msg) default: continue } } } func (cl *RemoteClient) destroy(s string) { if cl.isClosed { return } cl.isClosed = true close(cl.writeq) cl.conn.Write([]byte("ERROR :Closing link (" + s + ")")) cl.conn.Close() cl.server.DelClient <- cl if cl.name != "" { xlog.Info("Client '%s' disconnected: %s", cl.name, s) } else { xlog.Info("Client disconnected: %s", s) } } func (cl *RemoteClient) writeLine(format string, a ...interface{}) { cl.writeq <- fmt.Sprintf(format, a...) } func (cl *RemoteClient) connReader() { input := bufio.NewReader(cl.conn) for { s, err := input.ReadString('\n') if err == io.EOF { cl.destroy("Connection lost.") return } if err != nil { cl.destroy(fmt.Sprintf("Read error (%s).", err.Error())) 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 { cl.destroy("Connection lost.") return } else if err != nil { cl.destroy(fmt.Sprintf("Write error (%s).", err.Error())) return } written += n } } } var lineFuncs = map[string]func(*RemoteClient, *Message) bool{ CMD_PASS: handleLinePass, CMD_NICK: handleLineNick, CMD_USER: handleLineUser, // CMD_OPER: handleLineOper, // CMD_QUIT: handleLineQuit, CMD_JOIN: handleLineJoin, CMD_PART: handleLinePart, CMD_MODE: handleLineMode, // CMD_TOPIC: handleLineTopic, // CMD_NAMES: handleLineNames, // CMD_LIST: handleLineList, // CMD_INVITE: handleLineInvite, // CMD_KICK: handleLineKick, // CMD_VERSION: handleLineVersion, // CMD_STATS: handleLineStats, // CMD_TIME: handleLineTime, // CMD_ADMIN: handleLineAdmin, // CMD_INFO: handleLineInfo, CMD_PRIVMSG: handleLinePrivmsg, // CMD_NOTICE: handleLineNotice, // CMD_WHO: handleLineWho, // CMD_WHOIS: handleLineWhois, // CMD_WHOWAS: handleLineWhowas, // CMD_KILL: handleLineKill, CMD_PING: handleLinePing, // CMD_PONG: handleLinePong, // CMD_ERROR: handleLineError, // CMD_AWAY: handleLineAway, CMD_REHASH: handleLineRehash, // CMD_RESTART: handleLineRestart, // CMD_SUMMON: handleLineSummon, // CMD_USERS: handleLineUsers, // CMD_USERHOST: handleLineUserhost, // CMD_ISON: handleLineIson, } func (cl *RemoteClient) handleLine(s string) { xlog.Debug("Raw: [%s] '%s'.", cl.name, s) msg := M("", "", "", "", "", "") args := strings.SplitN(s, " :", 2) if len(args) > 1 { msg.Text = args[1] } args = strings.Fields(args[0]) msg.Cmd = strings.ToUpper(args[0]) if len(args) > 1 { msg.Args = args[1:] } msg.Src = cl.name msg.Dst = "" if _, exists := lineFuncs[msg.Cmd]; !exists { // xlog.Warning("No such command (%s).", msg.Cmd) return } if route := lineFuncs[msg.Cmd](cl, msg); route { // xlog.Warning("Routing to server (%s).", msg.Cmd) cl.Send(msg) } } func checkAuth(cl *RemoteClient) { if cl.name == "" || cl.password == "" { return } } func handleLinePass(cl *RemoteClient, msg *Message) bool { cl.password = msg.Args[0] return false } func handleLineNick(cl *RemoteClient, msg *Message) bool { if cl.name != "" { // TODO multiple registration not possible return false } /* if _, exists := cl.Server.Clients[msg.Args[0]]; exists { cl.destroy("User '" + msg.Args[0] + "' already connected.") } else { cl.name = msg.Args[0] cl.isRegistered = true cl.Server.AddClient <- cl xlog.Info("User '%s' registered.", msg.Args[0]) } */ 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 { cl.destroy("User '" + msg.Args[0] + "' already connected.") } return false } func handleLineUser(cl *RemoteClient, msg *Message) bool { return false } func handleLineJoin(cl *RemoteClient, msg *Message) bool { msg.Dst = msg.Args[0] msg.Args = make([]string, 0) return true } func handleLinePart(cl *RemoteClient, msg *Message) bool { msg.Dst = msg.Args[0] msg.Args = make([]string, 0) return true } func handleLineMode(cl *RemoteClient, msg *Message) bool { msg.Dst = msg.Args[0] msg.Args = make([]string, 0) return true } func handleLinePrivmsg(cl *RemoteClient, msg *Message) bool { msg.Dst = msg.Args[0] msg.Args = make([]string, 0) return true } func handleLinePing(cl *RemoteClient, msg *Message) bool { return true } func handleLineRehash(cl *RemoteClient, msg *Message) bool { return true } // vim:ts=4:sts=4:sw=4:noet:tw=72