USERHOST, VERSION, ADMIN, INFO, TIME, KILL and WALLOPS work on the HTTP API and on the IRC listener. The USERHOST reply, the VERSION and INFO text, KILL and WALLOPS are each one service function that both transports call. User mode +w selects who receives WALLOPS. MODE on your own nick queries or changes your user modes through one parser on both transports: the whole mode string is checked before anything is stored, and both flags are stored in one transaction. KILL closes the victim's IRC connection after sending it KILL and ERROR. The sessions table gains is_wallops in 001_initial.sql, as the pre-1.0 rule in REPO_POLICIES.md asks. Model: opus-5-5
This commit is contained in:
+134
-39
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
// sendIRCError maps a service.IRCError to an IRC numeric
|
||||
// reply on the wire.
|
||||
// reply on the wire, and logs any other error.
|
||||
func (c *Conn) sendIRCError(err error) {
|
||||
var ircErr *service.IRCError
|
||||
if errors.As(err, &ircErr) {
|
||||
@@ -21,7 +21,11 @@ func (c *Conn) sendIRCError(err error) {
|
||||
args = append(args, ircErr.Params...)
|
||||
args = append(args, ircErr.Message)
|
||||
c.sendNumeric(ircErr.Code, args...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.log.Error("command failed", "error", err)
|
||||
}
|
||||
|
||||
// handleCAP silently acknowledges CAP negotiation.
|
||||
@@ -345,7 +349,10 @@ func (c *Conn) handleQuit(msg *Message) {
|
||||
|
||||
c.send("ERROR :Closing Link: " + c.hostname +
|
||||
" (Quit: " + reason + ")")
|
||||
|
||||
c.mu.Lock()
|
||||
c.closed = true
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// handleTopic gets or sets a channel topic via the shared
|
||||
@@ -427,7 +434,7 @@ func (c *Conn) handleMode(
|
||||
if strings.HasPrefix(target, "#") {
|
||||
c.handleChannelMode(ctx, msg)
|
||||
} else {
|
||||
c.handleUserMode(msg)
|
||||
c.handleUserMode(ctx, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -686,11 +693,14 @@ func (c *Conn) applyChannelModes(
|
||||
}
|
||||
}
|
||||
|
||||
// handleUserMode handles MODE for users.
|
||||
func (c *Conn) handleUserMode(msg *Message) {
|
||||
target := msg.Params[0]
|
||||
|
||||
if !strings.EqualFold(target, c.nick) {
|
||||
// handleUserMode handles MODE for a nick: a query without a
|
||||
// mode string, otherwise a change. Only the client's own
|
||||
// nick, in any letter case, is allowed.
|
||||
func (c *Conn) handleUserMode(
|
||||
ctx context.Context,
|
||||
msg *Message,
|
||||
) {
|
||||
if !strings.EqualFold(msg.Params[0], c.currentNick()) {
|
||||
c.sendNumeric(
|
||||
irc.ErrUsersDoNotMatch,
|
||||
"Can't change mode for other users",
|
||||
@@ -699,8 +709,26 @@ func (c *Conn) handleUserMode(msg *Message) {
|
||||
return
|
||||
}
|
||||
|
||||
// We don't support user modes beyond the basics.
|
||||
c.sendNumeric(irc.RplUmodeIs, "+")
|
||||
var (
|
||||
modes string
|
||||
err error
|
||||
)
|
||||
|
||||
if len(msg.Params) == 1 {
|
||||
modes, err = c.svc.QueryUserMode(ctx, c.sessionID)
|
||||
} else {
|
||||
modes, err = c.svc.ApplyUserMode(
|
||||
ctx, c.sessionID, msg.Params[1],
|
||||
)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.sendIRCError(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
c.sendNumeric(irc.RplUmodeIs, modes)
|
||||
}
|
||||
|
||||
// handleNames replies with channel member list.
|
||||
@@ -1248,47 +1276,114 @@ func (c *Conn) handleInvite(
|
||||
)
|
||||
}
|
||||
|
||||
// handleUserhost replies with USERHOST info.
|
||||
// handleUserhost replies with user@host for each of up to
|
||||
// five nicks.
|
||||
func (c *Conn) handleUserhost(
|
||||
ctx context.Context,
|
||||
msg *Message,
|
||||
) {
|
||||
if len(msg.Params) < 1 {
|
||||
c.sendNumeric(
|
||||
irc.ErrNeedMoreParams,
|
||||
irc.CmdUserhost, "Not enough parameters",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
replies := make([]string, 0, len(msg.Params))
|
||||
reply, err := c.svc.UserhostReply(
|
||||
ctx, msg.Params, c.serverSfx,
|
||||
)
|
||||
if err != nil {
|
||||
c.sendIRCError(err)
|
||||
|
||||
for _, nick := range msg.Params {
|
||||
sid, err := c.database.GetSessionByNick(ctx, nick)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hostInfo, _ := c.database.GetSessionHostInfo(
|
||||
ctx, sid,
|
||||
)
|
||||
|
||||
host := "*"
|
||||
if hostInfo != nil {
|
||||
host = hostInfo.Hostname
|
||||
}
|
||||
|
||||
isOper, _ := c.database.IsSessionOper(ctx, sid)
|
||||
|
||||
operStar := ""
|
||||
if isOper {
|
||||
operStar = "*"
|
||||
}
|
||||
|
||||
replies = append(
|
||||
replies,
|
||||
nick+operStar+"=+"+nick+"@"+host,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
c.sendNumeric(irc.RplUserHost, reply)
|
||||
}
|
||||
|
||||
// handleVersion replies with the server version.
|
||||
func (c *Conn) handleVersion() {
|
||||
c.sendNumeric(
|
||||
irc.RplUserHost,
|
||||
strings.Join(replies, " "),
|
||||
irc.RplVersion,
|
||||
c.svc.ServerVersion()+".", c.serverSfx, "",
|
||||
)
|
||||
}
|
||||
|
||||
// handleAdmin replies with the server's admin info.
|
||||
func (c *Conn) handleAdmin() {
|
||||
c.sendNumeric(
|
||||
irc.RplAdminMe, c.serverSfx, "Administrative info",
|
||||
)
|
||||
c.sendNumeric(irc.RplAdminLoc1, "neoirc server")
|
||||
c.sendNumeric(irc.RplAdminLoc2, "IRC over HTTP")
|
||||
c.sendNumeric(irc.RplAdminEmail, "admin@"+c.serverSfx)
|
||||
}
|
||||
|
||||
// handleInfo replies with the server's software info.
|
||||
func (c *Conn) handleInfo() {
|
||||
for _, line := range c.svc.InfoLines() {
|
||||
c.sendNumeric(irc.RplInfo, line)
|
||||
}
|
||||
|
||||
c.sendNumeric(irc.RplEndOfInfo, "End of /INFO list")
|
||||
}
|
||||
|
||||
// handleTime replies with the server's local time.
|
||||
func (c *Conn) handleTime() {
|
||||
c.sendNumeric(
|
||||
irc.RplTime,
|
||||
c.serverSfx, time.Now().Format(time.RFC1123),
|
||||
)
|
||||
}
|
||||
|
||||
// handleKill handles KILL <nick> [<reason>].
|
||||
func (c *Conn) handleKill(
|
||||
ctx context.Context,
|
||||
msg *Message,
|
||||
) {
|
||||
if len(msg.Params) < 1 {
|
||||
c.sendNumeric(
|
||||
irc.ErrNeedMoreParams,
|
||||
irc.CmdKill, "Not enough parameters",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
reason := "KILLed"
|
||||
if len(msg.Params) > 1 {
|
||||
reason = msg.Params[1]
|
||||
}
|
||||
|
||||
err := c.svc.KillUser(
|
||||
ctx, c.sessionID, c.currentNick(),
|
||||
msg.Params[0], reason,
|
||||
)
|
||||
if err != nil {
|
||||
c.sendIRCError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleWallops handles WALLOPS <message>.
|
||||
func (c *Conn) handleWallops(
|
||||
ctx context.Context,
|
||||
msg *Message,
|
||||
) {
|
||||
if len(msg.Params) < 1 {
|
||||
c.sendNumeric(
|
||||
irc.ErrNeedMoreParams,
|
||||
irc.CmdWallops, "Not enough parameters",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := c.svc.SendWallops(
|
||||
ctx, c.sessionID, c.currentNick(), msg.Params[0],
|
||||
)
|
||||
if err != nil {
|
||||
c.sendIRCError(err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user