check / check (push) Waiting to run
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
308 lines
6.3 KiB
Go
308 lines
6.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"sneak.berlin/go/neoirc/pkg/irc"
|
|
)
|
|
|
|
// dispatchOperCommand routes the server operator commands
|
|
// OPER, KILL and WALLOPS to their handlers.
|
|
func (hdlr *Handlers) dispatchOperCommand(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick, command string,
|
|
bodyLines func() []string,
|
|
) {
|
|
switch command {
|
|
case irc.CmdOper:
|
|
hdlr.handleOper(
|
|
writer, request,
|
|
sessionID, clientID, nick, bodyLines,
|
|
)
|
|
case irc.CmdKill:
|
|
hdlr.handleKill(
|
|
writer, request,
|
|
sessionID, clientID, nick, bodyLines,
|
|
)
|
|
case irc.CmdWallops:
|
|
hdlr.handleWallops(
|
|
writer, request,
|
|
sessionID, clientID, nick, bodyLines,
|
|
)
|
|
}
|
|
}
|
|
|
|
// handleUserhost handles USERHOST: user@host for each of
|
|
// up to five nicks given in the body.
|
|
func (hdlr *Handlers) handleUserhost(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
bodyLines func() []string,
|
|
) {
|
|
lines := bodyLines()
|
|
if len(lines) == 0 {
|
|
hdlr.respondIRCError(
|
|
writer, request, clientID, sessionID,
|
|
irc.ErrNeedMoreParams, nick,
|
|
[]string{irc.CmdUserhost},
|
|
"Not enough parameters",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
reply, err := hdlr.svc.UserhostReply(
|
|
request.Context(), lines, hdlr.serverName(),
|
|
)
|
|
if hdlr.handleServiceError(
|
|
writer, request, clientID, sessionID, nick, err,
|
|
) {
|
|
return
|
|
}
|
|
|
|
hdlr.enqueueNumeric(
|
|
request.Context(), clientID,
|
|
irc.RplUserHost, nick, nil, reply,
|
|
)
|
|
hdlr.broker.Notify(sessionID)
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleVersion handles VERSION.
|
|
func (hdlr *Handlers) handleVersion(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
) {
|
|
hdlr.enqueueNumeric(
|
|
request.Context(), clientID, irc.RplVersion, nick,
|
|
[]string{
|
|
hdlr.svc.ServerVersion() + ".",
|
|
hdlr.serverName(),
|
|
},
|
|
"",
|
|
)
|
|
hdlr.broker.Notify(sessionID)
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleAdmin handles ADMIN.
|
|
func (hdlr *Handlers) handleAdmin(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
) {
|
|
ctx := request.Context()
|
|
srvName := hdlr.serverName()
|
|
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplAdminMe, nick,
|
|
[]string{srvName}, "Administrative info",
|
|
)
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplAdminLoc1, nick, nil,
|
|
"neoirc server",
|
|
)
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplAdminLoc2, nick, nil,
|
|
"IRC over HTTP",
|
|
)
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplAdminEmail, nick, nil,
|
|
"admin@"+srvName,
|
|
)
|
|
hdlr.broker.Notify(sessionID)
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleInfo handles INFO.
|
|
func (hdlr *Handlers) handleInfo(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
) {
|
|
ctx := request.Context()
|
|
|
|
for _, line := range hdlr.svc.InfoLines() {
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplInfo, nick, nil, line,
|
|
)
|
|
}
|
|
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplEndOfInfo, nick, nil,
|
|
"End of /INFO list",
|
|
)
|
|
hdlr.broker.Notify(sessionID)
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleTime handles TIME: the server's local time.
|
|
func (hdlr *Handlers) handleTime(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
) {
|
|
hdlr.enqueueNumeric(
|
|
request.Context(), clientID, irc.RplTime, nick,
|
|
[]string{hdlr.serverName()},
|
|
time.Now().Format(time.RFC1123),
|
|
)
|
|
hdlr.broker.Notify(sessionID)
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleKill handles KILL: the body is the target nick and
|
|
// an optional reason.
|
|
func (hdlr *Handlers) handleKill(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
bodyLines func() []string,
|
|
) {
|
|
lines := bodyLines()
|
|
|
|
targetNick := ""
|
|
if len(lines) > 0 {
|
|
targetNick = strings.TrimSpace(lines[0])
|
|
}
|
|
|
|
if targetNick == "" {
|
|
hdlr.respondIRCError(
|
|
writer, request, clientID, sessionID,
|
|
irc.ErrNeedMoreParams, nick,
|
|
[]string{irc.CmdKill},
|
|
"Not enough parameters",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
reason := "KILLed"
|
|
if len(lines) > 1 {
|
|
reason = lines[1]
|
|
}
|
|
|
|
err := hdlr.svc.KillUser(
|
|
request.Context(), sessionID, nick,
|
|
targetNick, reason,
|
|
)
|
|
if hdlr.handleServiceError(
|
|
writer, request, clientID, sessionID, nick, err,
|
|
) {
|
|
return
|
|
}
|
|
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleWallops handles WALLOPS: the body is the message.
|
|
func (hdlr *Handlers) handleWallops(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick string,
|
|
bodyLines func() []string,
|
|
) {
|
|
lines := bodyLines()
|
|
if len(lines) == 0 {
|
|
hdlr.respondIRCError(
|
|
writer, request, clientID, sessionID,
|
|
irc.ErrNeedMoreParams, nick,
|
|
[]string{irc.CmdWallops},
|
|
"Not enough parameters",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
err := hdlr.svc.SendWallops(
|
|
request.Context(), sessionID, nick,
|
|
strings.Join(lines, " "),
|
|
)
|
|
if hdlr.handleServiceError(
|
|
writer, request, clientID, sessionID, nick, err,
|
|
) {
|
|
return
|
|
}
|
|
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|
|
|
|
// handleUserMode handles MODE for a nick: a query when the
|
|
// body is empty, otherwise a change by the mode string in
|
|
// the body. Only the caller's own nick, in any letter case,
|
|
// is allowed.
|
|
func (hdlr *Handlers) handleUserMode(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
sessionID, clientID int64,
|
|
nick, target string,
|
|
bodyLines func() []string,
|
|
) {
|
|
if !strings.EqualFold(target, nick) {
|
|
hdlr.respondIRCError(
|
|
writer, request, clientID, sessionID,
|
|
irc.ErrUsersDoNotMatch, nick, nil,
|
|
"Can't change mode for other users",
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
ctx := request.Context()
|
|
|
|
var (
|
|
modes string
|
|
err error
|
|
)
|
|
|
|
lines := bodyLines()
|
|
if len(lines) > 0 {
|
|
modes, err = hdlr.svc.ApplyUserMode(
|
|
ctx, sessionID, lines[0],
|
|
)
|
|
} else {
|
|
modes, err = hdlr.svc.QueryUserMode(ctx, sessionID)
|
|
}
|
|
|
|
if hdlr.handleServiceError(
|
|
writer, request, clientID, sessionID, nick, err,
|
|
) {
|
|
return
|
|
}
|
|
|
|
hdlr.enqueueNumeric(
|
|
ctx, clientID, irc.RplUmodeIs, nick, nil, modes,
|
|
)
|
|
hdlr.broker.Notify(sessionID)
|
|
hdlr.respondJSON(writer, request,
|
|
map[string]string{statusKey: "ok"},
|
|
http.StatusOK)
|
|
}
|