aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRunxi Yu <me@runxiyu.org>2024-12-30 01:23:40 +0000
committerRunxi Yu <me@runxiyu.org>2024-12-30 01:23:40 +0000
commitdce03955174abe5ad83c01c930e3bf47fe39d8ce (patch)
treecf4cddc3f83bc6eb9efd5be959d349fa47606c84
parentReformat (diff)
downloadmeseircd-dce03955174abe5ad83c01c930e3bf47fe39d8ce.tar.gz
meseircd-dce03955174abe5ad83c01c930e3bf47fe39d8ce.tar.zst
meseircd-dce03955174abe5ad83c01c930e3bf47fe39d8ce.zip
Revamp
-rw-r--r--cap.go30
-rw-r--r--cap_sasl.go86
-rw-r--r--caps.go20
-rw-r--r--clients.go137
-rw-r--r--cmd.go5
-rw-r--r--cmd_cap.go84
-rw-r--r--cmd_nick.go45
-rw-r--r--cmd_ping.go12
-rw-r--r--cmd_user.go31
-rw-r--r--const.go7
-rw-r--r--errors.go21
-rw-r--r--go.mod4
-rw-r--r--go.sum2
-rw-r--r--main.go90
-rw-r--r--meselog/meselog.go27
-rw-r--r--msg.go158
-rw-r--r--numerics.go17
-rw-r--r--panics.go6
-rw-r--r--servers.go46
-rw-r--r--tags.go113
-rw-r--r--users.go95
-rw-r--r--util.go17
22 files changed, 36 insertions, 1017 deletions
diff --git a/cap.go b/cap.go
deleted file mode 100644
index 2beb771..0000000
--- a/cap.go
+++ /dev/null
@@ -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)
- }
-}
diff --git a/caps.go b/caps.go
new file mode 100644
index 0000000..bd78a9d
--- /dev/null
+++ b/caps.go
@@ -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
-)
diff --git a/cmd.go b/cmd.go
deleted file mode 100644
index a7f2ff5..0000000
--- a/cmd.go
+++ /dev/null
@@ -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")
-)
diff --git a/go.mod b/go.mod
index 6c5d231..472930c 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..dbcd25c
--- /dev/null
+++ b/go.sum
@@ -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=
diff --git a/main.go b/main.go
index e38974e..86ffa6a 100644
--- a/main.go
+++ b/main.go
@@ -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)
-}
diff --git a/msg.go b/msg.go
deleted file mode 100644
index 305add9..0000000
--- a/msg.go
+++ /dev/null
@@ -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
-}