diff --git a/commands/dispatch.go b/commands/dispatch.go index 5700a770..20106a9a 100644 --- a/commands/dispatch.go +++ b/commands/dispatch.go @@ -112,6 +112,7 @@ func init() { viper.BindPFlags(rootCmd.PersistentFlags()) viper.BindPFlags(rootCmd.Flags()) + viper.SetDefault("identd", true) viper.SetDefault("auto_ctcp", true) viper.SetDefault("verify_certificates", true) diff --git a/config.default.toml b/config.default.toml index 9378f79d..400fc222 100644 --- a/config.default.toml +++ b/config.default.toml @@ -1,6 +1,8 @@ # IP address to listen on, leave empty to listen on anything address = "" port = 80 +# Run ident daemon on port 113 +identd = true # Hex encode the users IP and use it as the ident hexIP = false # Automatically reply to common CTCP messages diff --git a/config/config.go b/config/config.go index f0b74048..42a471e6 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ type Config struct { Address string Port string Dev bool + Identd bool HexIP bool AutoCTCP bool `mapstructure:"auto_ctcp"` VerifyCertificates bool `mapstructure:"verify_certificates"` diff --git a/pkg/ident/server.go b/pkg/ident/server.go new file mode 100644 index 00000000..ce6f6bc5 --- /dev/null +++ b/pkg/ident/server.go @@ -0,0 +1,87 @@ +package ident + +import ( + "bufio" + "fmt" + "net" + "strings" + "sync" +) + +var ( + DefaultAddr = ":113" +) + +type Server struct { + Addr string + + idents map[string]string + listener net.Listener + lock sync.Mutex +} + +func NewServer() *Server { + return &Server{ + idents: map[string]string{}, + } +} + +func (s *Server) Listen() error { + var err error + + addr := s.Addr + if addr == "" { + addr = DefaultAddr + } + + s.listener, err = net.Listen("tcp", addr) + if err != nil { + return err + } + defer s.listener.Close() + + for { + conn, err := s.listener.Accept() + if err != nil { + return err + } + + go s.handle(conn) + } +} + +func (s *Server) Stop() error { + return s.listener.Close() +} + +func (s *Server) Add(local, remote, ident string) { + s.lock.Lock() + s.idents[local+","+remote] = ident + s.lock.Unlock() +} + +func (s *Server) Remove(local, remote string) { + s.lock.Lock() + delete(s.idents, local+","+remote) + s.lock.Unlock() +} + +func (s *Server) handle(conn net.Conn) { + defer conn.Close() + + scan := bufio.NewScanner(conn) + if !scan.Scan() { + return + } + + line := scan.Text() + ports := strings.ReplaceAll(line, " ", "") + + s.lock.Lock() + ident, ok := s.idents[ports] + s.lock.Unlock() + + if ok { + conn.Write([]byte(fmt.Sprintf("%s : USERID : Dispatch : %s\r\n", line, ident))) + } +} diff --git a/pkg/irc/client.go b/pkg/irc/client.go index 6aefb2be..c5bc1600 100644 --- a/pkg/irc/client.go +++ b/pkg/irc/client.go @@ -195,6 +195,19 @@ func (c *Client) Host() string { return c.Config.Host } +func (c *Client) LocalPort() string { + c.lock.Lock() + defer c.lock.Unlock() + + if c.conn != nil { + _, local, err := net.SplitHostPort(c.conn.LocalAddr().String()) + if err == nil { + return local + } + } + return "" +} + func (c *Client) MOTD() []string { return c.state.getMOTD() } diff --git a/pkg/irc/conn.go b/pkg/irc/conn.go index b97e6eda..554d8c61 100644 --- a/pkg/irc/conn.go +++ b/pkg/irc/conn.go @@ -22,7 +22,6 @@ type Dialer interface { } func (c *Client) Connect() { - c.connChange(false, nil) go c.run() } @@ -147,7 +146,7 @@ func (c *Client) connect() error { c.scan = bufio.NewScanner(c.conn) c.scan.Buffer(c.recvBuf, cap(c.recvBuf)) - c.register() + go c.register() c.sendRecv.Add(1) go c.recv() @@ -185,7 +184,7 @@ func (c *Client) recv() { return default: - c.connChange(false, nil) + c.connChange(false, c.scan.Err()) close(c.reconnect) return } @@ -198,8 +197,8 @@ func (c *Client) recv() { msg := ParseMessage(string(b)) if msg == nil { - close(c.quit) c.connChange(false, ErrBadProtocol) + close(c.quit) return } diff --git a/server/irc_handler.go b/server/irc_handler.go index b5d9efdb..687a52ca 100644 --- a/server/irc_handler.go +++ b/server/irc_handler.go @@ -46,6 +46,8 @@ func newIRCHandler(client *irc.Client, state *State) *ircHandler { func (i *ircHandler) run() { var lastConnErr error + var localPort string + for { select { case msg, ok := <-i.client.Messages: @@ -57,6 +59,16 @@ func (i *ircHandler) run() { i.dispatchMessage(msg) case state := <-i.client.ConnectionChanged: + if identd := i.state.srv.identd; identd != nil { + if state.Connected { + if localPort = i.client.LocalPort(); localPort != "" { + identd.Add(localPort, i.client.Config.Port, i.client.Config.Username) + } + } else { + identd.Remove(localPort, i.client.Config.Port) + } + } + i.state.sendJSON("connection_update", newConnectionUpdate(i.client.Host(), state)) if network, ok := i.state.network(i.client.Host()); ok { @@ -297,6 +309,16 @@ func (i *ircHandler) info(msg *irc.Message) { i.client.List() } + if identd := i.state.srv.identd; identd != nil { + if localPort := i.client.LocalPort(); localPort != "" { + identd.Remove(localPort, i.client.Config.Port) + } + } + + if network, ok := i.state.network(i.client.Host()); ok { + network.SetNick(msg.Params[0]) + } + go i.state.user.SetNick(msg.Params[0], i.client.Host()) } diff --git a/server/irc_handler_test.go b/server/irc_handler_test.go index be64ae7b..ec3c422a 100644 --- a/server/irc_handler_test.go +++ b/server/irc_handler_test.go @@ -56,7 +56,7 @@ func dispatchMessageMulti(msg *irc.Message) chan WSResponse { Username: "user", Host: "host.com", }) - s := NewState(user, nil) + s := NewState(user, &Dispatch{}) newIRCHandler(c, s).dispatchMessage(msg) diff --git a/server/server.go b/server/server.go index b9fc07d1..c0c4f87a 100644 --- a/server/server.go +++ b/server/server.go @@ -11,6 +11,7 @@ import ( "github.com/gorilla/websocket" "github.com/khlieng/dispatch/config" "github.com/khlieng/dispatch/pkg/https" + "github.com/khlieng/dispatch/pkg/ident" "github.com/khlieng/dispatch/pkg/session" "github.com/khlieng/dispatch/storage" ) @@ -24,6 +25,7 @@ type Dispatch struct { cfg *config.Config upgrader websocket.Upgrader states *stateStore + identd *ident.Server lock sync.Mutex } @@ -47,17 +49,24 @@ func (d *Dispatch) SetConfig(cfg *config.Config) { } func (d *Dispatch) Run() { + cfg := d.Config() + d.upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } - if d.Config().Dev { + if cfg.Dev { d.upgrader.CheckOrigin = func(r *http.Request) bool { return true } } + if cfg.Identd { + d.identd = ident.NewServer() + go d.identd.Listen() + } + session.CookieName = "dispatch" d.states = newStateStore(d.SessionStore)