diff options
-rw-r--r-- | cap.go | 30 | ||||
-rw-r--r-- | cap_sasl.go | 86 | ||||
-rw-r--r-- | caps.go | 20 | ||||
-rw-r--r-- | clients.go | 137 | ||||
-rw-r--r-- | cmd.go | 5 | ||||
-rw-r--r-- | cmd_cap.go | 84 | ||||
-rw-r--r-- | cmd_nick.go | 45 | ||||
-rw-r--r-- | cmd_ping.go | 12 | ||||
-rw-r--r-- | cmd_user.go | 31 | ||||
-rw-r--r-- | const.go | 7 | ||||
-rw-r--r-- | errors.go | 21 | ||||
-rw-r--r-- | go.mod | 4 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | main.go | 90 | ||||
-rw-r--r-- | meselog/meselog.go | 27 | ||||
-rw-r--r-- | msg.go | 158 | ||||
-rw-r--r-- | numerics.go | 17 | ||||
-rw-r--r-- | panics.go | 6 | ||||
-rw-r--r-- | servers.go | 46 | ||||
-rw-r--r-- | tags.go | 113 | ||||
-rw-r--r-- | users.go | 95 | ||||
-rw-r--r-- | util.go | 17 |
22 files changed, 36 insertions, 1017 deletions
@@ -1,30 +0,0 @@ -package main - -import ( - "strings" -) - -var Caps = map[string]string{} - -var ( - capls string - capls302 string -) - -// Can't be in init() because Caps will be registered with init in the future -// and init()s are executed by filename alphabetical order -func setupCapls() { - capls = "" - capls302 = "" - for k, v := range Caps { - capls += k - capls302 += k - if v != "" { - capls302 += "=" + v - } - capls += " " - capls302 += " " - } - capls = strings.TrimSuffix(capls, " ") - capls302 = strings.TrimSuffix(capls302, " ") -} diff --git a/cap_sasl.go b/cap_sasl.go deleted file mode 100644 index feb1ab4..0000000 --- a/cap_sasl.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "bytes" - "encoding/base64" -) - -type ExtraSasl struct { - AuthMethod string -} - -const ( - RPL_LOGGEDIN = "900" - RPL_LOGGEDOUT = "901" - ERR_NICKLOCKED = "902" - RPL_SASLSUCCESS = "903" - ERR_SASLFAIL = "904" - ERR_SASLTOOLONG = "905" - ERR_SASLABORTED = "906" - ERR_SASLALREADY = "907" - RPL_SASLMECHS = "908" -) - -const ( - panicSaslMethod = "stored illegal SASL method" -) - -func init() { - Caps["sasl"] = "PLAIN,EXTERNAL" - CommandHandlers["AUTHENTICATE"] = handleClientAuthenticate -} - -func handleClientAuthenticate(msg RMsg, client *Client) error { - _, ok := client.Caps["sasl"] - if !ok { - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed (capability not requested)")) - } - - if len(msg.Params) < 1 { - return client.Send(MakeMsg(self, ERR_NEEDMOREPARAMS, "AUTHENTICATE", "Not enough parameters")) - } - - extraSasl_, ok := client.Extra["sasl"] - if !ok { - client.Extra["sasl"] = &ExtraSasl{} - extraSasl_ = client.Extra["sasl"] - } - extraSasl, ok := extraSasl_.(*ExtraSasl) - if !ok { - panic(panicType) - } - - switch extraSasl.AuthMethod { - case "": - if msg.Params[0] != "PLAIN" && msg.Params[0] != "EXTERNAL" { - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed (invalid method)")) - } - extraSasl.AuthMethod = msg.Params[0] - return client.Send(MakeMsg(self, "AUTHENTICATE", "+")) - case "*": // Abort - extraSasl.AuthMethod = "" - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed (aborted)")) - case "EXTERNAL": - extraSasl.AuthMethod = "" - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed")) - case "PLAIN": - extraSasl.AuthMethod = "" - saslPlainData, err := base64.StdEncoding.DecodeString(msg.Params[0]) - if err != nil { - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed (base64 decoding error)")) - } - saslPlainSegments := bytes.Split(saslPlainData, []byte{0}) - if len(saslPlainSegments) != 3 { - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed (not three segments)")) - } - _ = string(saslPlainSegments[0]) // authzid unused - authcid := string(saslPlainSegments[1]) - passwd := string(saslPlainSegments[2]) - if authcid == "runxiyu" && passwd == "hunter2" { - return client.Send(MakeMsg(self, RPL_SASLSUCCESS, client.Nick, "SASL authentication successful")) - } - return client.Send(MakeMsg(self, ERR_SASLFAIL, client.Nick, "SASL authentication failed")) - default: - panic(panicSaslMethod) - } -} @@ -0,0 +1,20 @@ +package main + +import ( + "strings" +) + +var global_cap_ls_response string +var global_cap_ls_302_response string + +// Register a new capability. +// This function is not reentrant and must not be called concurrently. +// It should only be used in the init() functions in source files. +func register_cap(name string, parameters string) { + global_cap_ls_response += name + " " + global_cap_ls_302_response += name + if parameters != "" { + global_cap_ls_302_response += "=" + parameters + } + global_cap_ls_302_response += " " +} diff --git a/clients.go b/clients.go deleted file mode 100644 index 0744bcf..0000000 --- a/clients.go +++ /dev/null @@ -1,137 +0,0 @@ -package main - -import ( - "net" - "sync" - - "git.sr.ht/~runxiyu/meseircd/meselog" -) - -type Client struct { - conn *net.Conn - CID uint64 - Nick string - Ident string - Gecos string - Host string - Caps map[string]struct{} - Extra map[string]any - Server *Server - State ClientState -} - -func (client *Client) Send(msg SMsg) error { - return client.SendRaw(msg.ClientSerialize()) -} - -func (client *Client) SendRaw(s string) error { - if client.conn == nil { - panic("not implemented") - } - meselog.Debug("send", "line", s, "client", client.CID) - _, err := (*client.conn).Write([]byte(s)) - if err != nil { - // TODO: Should shut down the netFd instead but the stdlib - // doesn't expose a way to do this. - (*client.conn).Close() - return err - } - return nil -} - -func (client *Client) ClientSource() string { - // TODO: Edge cases where these aren't available - return client.Nick + "!" + client.Ident + "@" + client.Host -} - -func (client *Client) ServerSource() uint64 { - return client.CID -} - -func (client *Client) Teardown() { - if client.conn != nil { - (*client.conn).Close() - } - if !cidToClient.CompareAndDelete(client.CID, client) { - meselog.Error("cid inconsistent", "cid", client.CID, "client", client) - } - if client.State >= ClientStateRegistered || client.Nick != "*" { - if !nickToClient.CompareAndDelete(client.Nick, client) { - meselog.Error("nick inconsistent", "nick", client.Nick, "client", client) - } - } -} - -func NewLocalClient(conn *net.Conn) (*Client, error) { - var cidPart uint32 - { - cidPartCountLock.Lock() - defer cidPartCountLock.Unlock() - if cidPartCount == ^uint32(0) { // UINT32_MAX - return nil, ErrFullClients - } - cidPartCount++ - cidPart = cidPartCount - } - client := &Client{ - conn: conn, - Server: self, - State: ClientStatePreRegistration, - Nick: "*", - Caps: make(map[string]struct{}), - Extra: make(map[string]any), - CID: uint64(self.SID)<<32 | uint64(cidPart), - } - return client, nil -} - -func (client *Client) checkRegistration() error { - switch client.State { - case ClientStatePreRegistration: - case ClientStateCapabilitiesFinished: - default: - return nil - } - if client.Nick == "*" || client.Ident == "" { - return nil - } - client.State = ClientStateRegistered - err := client.Send(MakeMsg(self, RPL_WELCOME, client.Nick, "Welcome to the rxIRC network, "+client.Nick)) - if err != nil { - return err - } - err = client.Send(MakeMsg(self, RPL_YOURHOST, client.Nick, "Your host is "+self.Name+", running version "+VERSION)) - if err != nil { - return err - } - err = client.Send(MakeMsg(self, RPL_CREATED, client.Nick, "This server was created 1970-01-01 00:00:00 UTC")) - if err != nil { - return err - } - err = client.Send(MakeMsg(self, RPL_MYINFO, client.Nick, self.Name, VERSION, "", "", "")) - if err != nil { - return err - } - err = client.Send(MakeMsg(self, RPL_ISUPPORT, "YAY=", "are supported by this server")) - if err != nil { - return err - } - return nil -} - -type ClientState uint8 - -const ( - ClientStatePreRegistration ClientState = iota - ClientStateCapabilities - ClientStateCapabilitiesFinished - ClientStateRegistered - ClientStateRemote -) - -var ( - cidToClient = sync.Map{} - nickToClient = sync.Map{} - cidPartCount uint32 - cidPartCountLock sync.Mutex -) @@ -1,5 +0,0 @@ -package main - -var CommandHandlers = map[string](func(RMsg, *Client) error){} - -/* Maybe we should make command handlers return their values for easier labelled-reply? */ diff --git a/cmd_cap.go b/cmd_cap.go deleted file mode 100644 index 1f641f4..0000000 --- a/cmd_cap.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "strconv" - "strings" -) - -func init() { - CommandHandlers["CAP"] = handleClientCap -} - -func handleClientCap(msg RMsg, client *Client) error { - if len(msg.Params) < 1 { - err := client.Send(MakeMsg(self, ERR_NEEDMOREPARAMS, "CAP", "Not enough parameters")) - if err != nil { - return err - } - return nil - } - if client.State == ClientStateRemote { - return ErrRemoteClient - } - switch strings.ToUpper(msg.Params[0]) { - case "LS": - if client.State == ClientStatePreRegistration { - client.State = ClientStateCapabilities - } - var err error - if len(msg.Params) >= 2 { - capVersion, err := strconv.ParseUint(msg.Params[1], 10, 64) - if err == nil && capVersion >= 302 { - err = client.Send(MakeMsg(self, "CAP", client.Nick, "LS", capls302)) - } else { - err = client.Send(MakeMsg(self, "CAP", client.Nick, "LS", capls)) - } - if err != nil { - return err - } - } else { - err = client.Send(MakeMsg(self, "CAP", client.Nick, "LS", capls)) - } - // TODO: Split when too long - if err != nil { - return err - } - case "REQ": - if client.State == ClientStatePreRegistration { - client.State = ClientStateCapabilities - } - caps := strings.Split(msg.Params[1], " ") - for _, c := range caps { - if c[0] == '-' { - // TODO: Remove capability - delete(client.Caps, c) - continue - } - _, ok := Caps[c] - if ok { - err := client.Send(MakeMsg(self, "CAP", client.Nick, "ACK", c)) - if err != nil { - return err - } - client.Caps[c] = struct{}{} - // TODO: This is terrible - } else { - err := client.Send(MakeMsg(self, "CAP", client.Nick, "NAK", c)) - if err != nil { - return err - } - } - } - case "END": - if client.State != ClientStateCapabilities { - // Just ignore it - return nil - } - client.State = ClientStateCapabilitiesFinished - err := client.checkRegistration() - if err != nil { - return err - } - } - return nil -} diff --git a/cmd_nick.go b/cmd_nick.go deleted file mode 100644 index deff08b..0000000 --- a/cmd_nick.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "fmt" - - "git.sr.ht/~runxiyu/meseircd/meselog" -) - -func init() { - CommandHandlers["NICK"] = handleClientNick -} - -func handleClientNick(msg RMsg, client *Client) error { - if len(msg.Params) < 1 { - return client.Send(MakeMsg(self, ERR_NEEDMOREPARAMS, "NICK", "Not enough parameters")) - } - already, exists := nickToClient.LoadOrStore(msg.Params[0], client) - if exists { - if already != client { - err := client.Send(MakeMsg(self, ERR_NICKNAMEINUSE, client.Nick, msg.Params[0], "Nickname is already in use")) - if err != nil { - return err - } - } - } else { - if (client.State >= ClientStateRegistered || client.Nick != "*") && !nickToClient.CompareAndDelete(client.Nick, client) { - meselog.Error("nick inconsistent", "nick", client.Nick, "client", client) - return fmt.Errorf("%w: %v", ErrInconsistentClient, client) - } - if client.State == ClientStateRegistered { - err := client.Send(MakeMsg(client, "NICK", msg.Params[0])) - if err != nil { - return err - } - } - client.Nick = msg.Params[0] - } - if client.State < ClientStateRegistered { - err := client.checkRegistration() - if err != nil { - return err - } - } - return nil -} diff --git a/cmd_ping.go b/cmd_ping.go deleted file mode 100644 index 951233e..0000000 --- a/cmd_ping.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -func init() { - CommandHandlers["PING"] = handleClientPing -} - -func handleClientPing(msg RMsg, client *Client) error { - if len(msg.Params) < 1 { - return client.Send(MakeMsg(self, ERR_NEEDMOREPARAMS, "PING", "Not enough parameters")) - } - return client.Send(MakeMsg(self, "PONG", self.Name, msg.Params[0])) -} diff --git a/cmd_user.go b/cmd_user.go deleted file mode 100644 index 93ee17e..0000000 --- a/cmd_user.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( -// "log/slog" -) - -func init() { - CommandHandlers["USER"] = handleClientUser -} - -func handleClientUser(msg RMsg, client *Client) error { - if len(msg.Params) < 4 { - return client.Send(MakeMsg(self, ERR_NEEDMOREPARAMS, "USER", "Not enough parameters")) - } - switch { - case client.State < ClientStateRegistered: - client.Ident = "~" + msg.Params[0] - client.Gecos = msg.Params[3] - err := client.checkRegistration() - if err != nil { - return err - } - case client.State == ClientStateRegistered: - err := client.Send(MakeMsg(self, ERR_ALREADYREGISTERED, client.Nick, "You may not reregister")) - if err != nil { - return err - } - case client.State == ClientStateRemote: - } - return nil -} diff --git a/const.go b/const.go deleted file mode 100644 index 962db26..0000000 --- a/const.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -const ( - MaxlenTags = 8191 - MaxlenTagData = MaxlenTags - 2 - MaxlenBody = 510 -) diff --git a/errors.go b/errors.go deleted file mode 100644 index 98a81b3..0000000 --- a/errors.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "errors" -) - -var ( - ErrEmptyMessage = errors.New("empty message") - ErrIllegalByte = errors.New("illegal byte") - ErrTagsTooLong = errors.New("tags too long") - ErrInvalidTagContent = errors.New("invalid tag content") - ErrBodyTooLong = errors.New("body too long") - ErrNotConnectedServer = errors.New("not connected server") - ErrSendToSelf = errors.New("attempt to send message to self") - ErrCIDBusy = errors.New("too many busy cids") - ErrCallState = errors.New("invalid call state") - ErrInconsistentGlobal = errors.New("inconsistent global state") - ErrInconsistentClient = errors.New("inconsistent client state") - ErrRemoteClient = errors.New("operation not supported for a remote client") - ErrFullClients = errors.New("this server has/had too many clients") -) @@ -1,3 +1,5 @@ module git.sr.ht/~runxiyu/meseircd -go 1.22 +go 1.23.4 + +require go.lindenii.runxiyu.org/lindenii-common v0.0.0-20241229193139-603ef6227aca @@ -0,0 +1,2 @@ +go.lindenii.runxiyu.org/lindenii-common v0.0.0-20241229193139-603ef6227aca h1:iyxIKtyBNe5RlIMVu+whX2r2JGK9JCo6RotHrHstb0U= +go.lindenii.runxiyu.org/lindenii-common v0.0.0-20241229193139-603ef6227aca/go.mod h1:bOxuuGXA3UpbLb1lKohr2j2MVcGGLcqfAprGx9VCkMA= @@ -1,109 +1,41 @@ package main import ( - "bufio" - "log" "net" - "git.sr.ht/~runxiyu/meseircd/meselog" + "go.lindenii.runxiyu.org/lindenii-common/clog" ) const VERSION = "MeseIRCd-0.0.0" func main() { - setupCapls() + // TODO: Parse config and add TLS - self = &Server{ + self = &server_t{ conn: nil, - SID: 0, - Name: "irc.runxiyu.org", + sid: 0, + name: "irc.runxiyu.org", } listener, err := net.Listen("tcp", ":6667") if err != nil { - log.Fatal(err) + clog.Fatal(1, "listen", "error", err) } defer listener.Close() for { conn, err := listener.Accept() if err != nil { - log.Fatal(err) + clog.Fatal(1, "accept", "error", err) } - go func() { - defer func() { - raised := recover() - if raised != nil { - meselog.Error("connection routine panicked", "raised", raised) - } - }() defer conn.Close() - client, err := NewLocalClient(&conn) + client, err := new_local_client(&conn) if err != nil { - meselog.Error("cannot make new local client", "error", err) + clog.Error("new_local_client", "error", err, "conn", &conn) } - defer client.Teardown() - client.handleConnection() + defer client.teardown() + client.handle_connection() }() } } - -func (client *Client) handleConnection() { - reader := bufio.NewReader(*client.conn) -messageLoop: - for { - line, err := reader.ReadString('\n') - if err != nil { - meselog.Error("error while reading from connection", "error", err) - (*client.conn).Close() - return - } - meselog.Debug("recv", "line", line, "client", client.CID) - msg, err := parseIRCMsg(line) - if err != nil { - switch err { - case ErrEmptyMessage: - continue messageLoop - case ErrIllegalByte: - err := client.Send(MakeMsg(self, "ERROR", err.Error())) - if err != nil { - meselog.Error("error while reporting illegal byte", "error", err, "client", client) - return - } - return - case ErrTagsTooLong: - fallthrough - case ErrBodyTooLong: - err := client.Send(MakeMsg(self, ERR_INPUTTOOLONG, err.Error())) - if err != nil { - meselog.Error("error while reporting body too long", "error", err, "client", client) - return - } - continue messageLoop - default: - err := client.Send(MakeMsg(self, "ERROR", err.Error())) - if err != nil { - meselog.Error("error while reporting parser error", "error", err, "client", client) - } - return - } - } - - handler, ok := CommandHandlers[msg.Command] - if !ok { - err := client.Send(MakeMsg(self, ERR_UNKNOWNCOMMAND, msg.Command, "Unknown command")) - if err != nil { - meselog.Error("error while reporting unknown command", "error", err, "client", client) - return - } - continue - } - - err = handler(msg, client) - if err != nil { - meselog.Error("handler error", "error", err, "client", client) - return - } - } -} diff --git a/meselog/meselog.go b/meselog/meselog.go deleted file mode 100644 index 402d674..0000000 --- a/meselog/meselog.go +++ /dev/null @@ -1,27 +0,0 @@ -package meselog - -import ( - "fmt" -) - -func log(str string, keyvals []any) { - fmt.Print(str + " ") - for i, j := range keyvals { - if i&1 == 0 { - fmt.Printf("%v=", j) - } else if i == len(keyvals)-1 { - fmt.Printf("%#v", j) - } else { - fmt.Printf("%#v ", j) - } - } - fmt.Print("\n") -} - -func Error(str string, keyvals ...any) { - log("ERROR "+str, keyvals) -} - -func Debug(str string, keyvals ...any) { - log("DEBUG "+str, keyvals) -} @@ -1,158 +0,0 @@ -package main - -import ( - "strings" -) - -type RMsg struct { - RawSource string - Command string - Tags map[string]string - Params []string -} - -type Sourceable interface { - ClientSource() string - ServerSource() uint64 -} - -type SMsg struct { - Source Sourceable - Command string - Tags map[string]string - Params []string -} - -func (msg *SMsg) ClientSerialize() (final string) { - if len(msg.Tags) != 0 { - final = "@" - for k, v := range msg.Tags { - // TODO: Tag values must be escaped - final += k + "=" + v + ";" - } - final += " " - } - if msg.Source != nil { - final += ":" + msg.Source.ClientSource() + " " - } - final += msg.Command + " " - - if len(msg.Params) > 0 { - for i := 0; i < len(msg.Params)-1; i++ { - final += msg.Params[i] + " " - } - final += ":" + msg.Params[len(msg.Params)-1] - } - final += "\n" - return -} - -func (msg *SMsg) ServerSerialize() (final string) { - panic(panicNotImplemented) -} - -func MakeMsg(source Sourceable, command string, params ...string) (msg SMsg) { - // TODO: Add tags - return SMsg{Source: source, Command: command, Params: params} -} - -// Partially adapted from https://github.com/ergochat/irc-go.git -func parseIRCMsg(line string) (msg RMsg, err error) { - msg = RMsg{} - - line = strings.TrimSuffix(line, "\n") - line = strings.TrimSuffix(line, "\r") - - if len(line) == 0 { - err = ErrEmptyMessage - return - } - - for _, v := range line { - if v == '\x00' || v == '\r' || v == '\n' { - err = ErrIllegalByte - return - } - } - - // IRCv3 tags - if line[0] == '@' { - tagEnd := strings.IndexByte(line, ' ') - if tagEnd == -1 { - err = ErrEmptyMessage - return - } - tagsString := line[1:tagEnd] - if 0 < MaxlenTagData && MaxlenTagData < len(tagsString) { - err = ErrTagsTooLong - return - } - msg.Tags, err = parseTags(tagsString) - if err != nil { - return - } - // Skip over the tags and the separating space - line = line[tagEnd+1:] - } - - if len(line) > MaxlenBody { - err = ErrBodyTooLong - line = line[:MaxlenBody] - } - - line = trimInitialSpaces(line) - - // Source - if 0 < len(line) && line[0] == ':' { - sourceEnd := strings.IndexByte(line, ' ') - if sourceEnd == -1 { - err = ErrEmptyMessage - return - } - msg.RawSource = line[1:sourceEnd] - // Skip over the source and the separating space - line = line[sourceEnd+1:] - } - - // Command - commandEnd := strings.IndexByte(line, ' ') - paramStart := commandEnd + 1 - if commandEnd == -1 { - commandEnd = len(line) - paramStart = len(line) - } - baseCommand := line[:commandEnd] - if len(baseCommand) == 0 { - err = ErrEmptyMessage - return - } - // TODO: Actually must be either letters or a 3-digit numeric - if !isASCII(baseCommand) { - err = ErrIllegalByte - return - } - msg.Command = strings.ToUpper(baseCommand) - line = line[paramStart:] - - // Other arguments - for { - line = trimInitialSpaces(line) - if len(line) == 0 { - break - } - // Trailing - if line[0] == ':' { - msg.Params = append(msg.Params, line[1:]) - break - } - paramEnd := strings.IndexByte(line, ' ') - if paramEnd == -1 { - msg.Params = append(msg.Params, line) - break - } - msg.Params = append(msg.Params, line[:paramEnd]) - line = line[paramEnd+1:] - } - - return -} diff --git a/numerics.go b/numerics.go deleted file mode 100644 index dcf7812..0000000 --- a/numerics.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -const ( - RPL_WELCOME = "001" - RPL_YOURHOST = "002" - RPL_CREATED = "003" - RPL_MYINFO = "004" - RPL_ISUPPORT = "005" -) - -const ( - ERR_UNKNOWNCOMMAND = "421" - ERR_INPUTTOOLONG = "417" - ERR_NEEDMOREPARAMS = "461" - ERR_NICKNAMEINUSE = "433" - ERR_ALREADYREGISTERED = "462" -) diff --git a/panics.go b/panics.go deleted file mode 100644 index 7f05d28..0000000 --- a/panics.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -const ( - panicType = "type error" - panicNotImplemented = "not implemented" -) diff --git a/servers.go b/servers.go deleted file mode 100644 index af31093..0000000 --- a/servers.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "net" - - "git.sr.ht/~runxiyu/meseircd/meselog" -) - -type Server struct { - conn *net.Conn - SID uint32 - Name string -} - -func (server *Server) Send(msg SMsg) error { - return server.SendRaw(msg.ServerSerialize()) -} - -func (server *Server) SendRaw(s string) error { - if server == self { - return ErrSendToSelf - } - if server.conn == nil { - // TODO: Propagate across mesh - return ErrNotConnectedServer - } - meselog.Debug("send", "line", s, "conn", server.conn) - _, err := (*server.conn).Write([]byte(s)) - if err != nil { - // TODO: Should shut down the netFd instead but the stdlib - // doesn't expose a way to do this. - (*server.conn).Close() - return err - } - return nil -} - -func (server *Server) ClientSource() string { - return server.Name -} - -func (server *Server) ServerSource() uint64 { - return uint64(server.SID) << 32 -} - -var self *Server diff --git a/tags.go b/tags.go deleted file mode 100644 index 9fb95f5..0000000 --- a/tags.go +++ /dev/null @@ -1,113 +0,0 @@ -// Almost everything in this file is adapted from Ergo IRCd -// This is probably considered a derived work for copyright purposes - -package main - -import ( - "strings" - "unicode/utf8" -) - -func parseTags(tagsString string) (tags map[string]string, err error) { - tags = make(map[string]string) - for 0 < len(tagsString) { - tagEnd := strings.IndexByte(tagsString, ';') - endPos := tagEnd - nextPos := tagEnd + 1 - if tagEnd == -1 { - endPos = len(tagsString) - nextPos = len(tagsString) - } - tagPair := tagsString[:endPos] - equalsIndex := strings.IndexByte(tagPair, '=') - var tagName, tagValue string - if equalsIndex == -1 { - // Tag with no value - tagName = tagPair - } else { - tagName, tagValue = tagPair[:equalsIndex], tagPair[equalsIndex+1:] - } - // "Implementations [...] MUST NOT perform any validation that would - // reject the message if an invalid tag key name is used." - if validateTagName(tagName) { - // "Tag values MUST be encoded as UTF8." - if !utf8.ValidString(tagValue) { - err = ErrInvalidTagContent - return - } - tags[tagName] = UnescapeTagValue(tagValue) - } - tagsString = tagsString[nextPos:] - } - return -} - -func UnescapeTagValue(inString string) string { - // buf.Len() == 0 is the fastpath where we have not needed to unescape any chars - var buf strings.Builder - remainder := inString - for { - backslashPos := strings.IndexByte(remainder, '\\') - - if backslashPos == -1 { - if buf.Len() == 0 { - return inString - } else { - buf.WriteString(remainder) - break - } - } else if backslashPos == len(remainder)-1 { - // trailing backslash, which we strip - if buf.Len() == 0 { - return inString[:len(inString)-1] - } else { - buf.WriteString(remainder[:len(remainder)-1]) - break - } - } - - // Non-trailing backslash detected; we're now on the slowpath - // where we modify the string - if buf.Len() < len(inString) { - buf.Grow(len(inString)) - } - buf.WriteString(remainder[:backslashPos]) - buf.WriteByte(escapedCharLookupTable[remainder[backslashPos+1]]) - remainder = remainder[backslashPos+2:] - } - - return buf.String() -} - -var escapedCharLookupTable [256]byte - -func init() { - for i := 0; i < 256; i += 1 { - escapedCharLookupTable[i] = byte(i) - } - escapedCharLookupTable[':'] = ';' - escapedCharLookupTable['s'] = ' ' - escapedCharLookupTable['r'] = '\r' - escapedCharLookupTable['n'] = '\n' -} - -// https://ircv3.net/specs/extensions/message-tags.html#rules-for-naming-message-tags -func validateTagName(name string) bool { - if len(name) == 0 { - return false - } - if name[0] == '+' { - name = name[1:] - } - if len(name) == 0 { - return false - } - // Let's err on the side of leniency here; allow -./ (45-47) in any position - for i := 0; i < len(name); i++ { - c := name[i] - if !(('-' <= c && c <= '/') || ('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) { - return false - } - } - return true -} diff --git a/users.go b/users.go deleted file mode 100644 index d94a0fe..0000000 --- a/users.go +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "net" - "sync" - - "git.sr.ht/~runxiyu/meseircd/meselog" -) - -type User struct { - Clients []*Client - UID uint64 - Nick string - Ident string - Gecos string - Host string - Caps map[string]struct{} - Extra map[string]any - Server *Server - State ClientState -} - -func (user *User) SendToLocalClients(msg SMsg) (numSent uint) { - for _, c := range user.Clients { - if c.Server != self { - continue - } - err := c.Send(msg) - if err == nil { - numSent++ - } - } - return -} - -func (user *User) ClientSource() string { - // TODO: Edge cases where these aren't available - return user.Nick + "!" + user.Ident + "@" + user.Host -} - -func (user *User) ServerSource() uint64 { - return user.UID -} - -// func (user *User) Delete() { -// if client.conn != nil { -// (*client.conn).Close() -// } -// if !cidToClient.CompareAndDelete(client.CID, client) { -// meselog.Error("cid inconsistent", "cid", client.CID, "client", client) -// } -// if client.State >= ClientStateRegistered || client.Nick != "*" { -// if !nickToClient.CompareAndDelete(client.Nick, client) { -// meselog.Error("nick inconsistent", "nick", client.Nick, "client", client) -// } -// } -// } - -func NewLocalUser(conn *net.Conn) (*User, error) { - var uidPart uint32 - { - uidPartCountLock.Lock() - defer uidPartCountLock.Unlock() - if uidPartCount == ^uint32(0) { // UINT32_MAX - return nil, ErrFullClients - } - uidPartCount++ - uidPart = uidPartCount - } - client := &Client{ - conn: conn, - Server: self, - State: ClientStatePreRegistration, - Nick: "*", - Caps: make(map[string]struct{}), - Extra: make(map[string]any), - CID: uint64(self.SID)<<32 | uint64(uidPart), - } - return client, nil -} - -const ( - ClientStatePreRegistration ClientState = iota - ClientStateCapabilities - ClientStateCapabilitiesFinished - ClientStateRegistered - ClientStateRemote -) - -var ( - cidToClient = sync.Map{} - nickToClient = sync.Map{} - uidPartCount uint32 - uidPartCountLock sync.Mutex -) diff --git a/util.go b/util.go deleted file mode 100644 index 8754348..0000000 --- a/util.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -func trimInitialSpaces(line string) string { - var i int - for i = 0; i < len(line) && line[i] == ' '; i++ { - } - return line[i:] -} - -func isASCII(str string) bool { - for i := 0; i < len(str); i++ { - if str[i] > 127 { - return false - } - } - return true -} |