README.md rules that nicks are case-sensitive, but the IRC listener's relay and user MODE on both transports compared nicks ignoring case: an IRC listener user alice never saw a PRIVMSG, NOTICE, JOIN, PART, NICK or QUIT from Alice, and MODE Alice from alice was taken as her own nick. These checks now use ==. Tests cover alice receiving each of these from Alice, MODE Alice from alice getting 502 on both transports, and Alice registering while alice exists. README.md drops the text that described the old behaviour. Model: opus-5-5
This commit was merged in pull request #141.
This commit is contained in:
@@ -1031,14 +1031,14 @@ e.g. `+o-v alice bob`.
|
|||||||
| `441` | ERR_USERNOTINCHANNEL | The `+o`/`+v` nick is not in the channel |
|
| `441` | ERR_USERNOTINCHANNEL | The `+o`/`+v` nick is not in the channel |
|
||||||
|
|
||||||
**User modes:** you can query and change only your own; a nick other than your
|
**User modes:** you can query and change only your own; a nick other than your
|
||||||
own, compared without regard to letter case, gets ERR_USERSDONTMATCH (502). With
|
own gets ERR_USERSDONTMATCH (502). With no mode string the server sends
|
||||||
no mode string the server sends RPL_UMODEIS (221) with your modes: `+`, `+o`,
|
RPL_UMODEIS (221) with your modes: `+`, `+o`, `+w` or `+ow`. A change is a mode
|
||||||
`+w` or `+ow`. A change is a mode string, `body[0]` over the HTTP API or the
|
string, `body[0]` over the HTTP API or the parameter after the nick on the IRC
|
||||||
parameter after the nick on the IRC listener, that starts with `+` or `-`: `w`
|
listener, that starts with `+` or `-`: `w` (receive `WALLOPS`) can be set or
|
||||||
(receive `WALLOPS`) can be set or unset, and `o` (server operator) only unset,
|
unset, and `o` (server operator) only unset, since only `OPER` sets it. Any
|
||||||
since only `OPER` sets it. Any other letter, or a string that names no mode,
|
other letter, or a string that names no mode, gets ERR_UMODEUNKNOWNFLAG (501)
|
||||||
gets ERR_UMODEUNKNOWNFLAG (501) and changes nothing. A change that is accepted
|
and changes nothing. A change that is accepted is answered with 221 and the new
|
||||||
is answered with 221 and the new modes; it is not sent to anyone else.
|
modes; it is not sent to anyone else.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "command": "RPL_UMODEIS", "code": 221, "to": "alice", "body": ["+w"] }
|
{ "command": "RPL_UMODEIS", "code": 221, "to": "alice", "body": ["+w"] }
|
||||||
@@ -2679,8 +2679,7 @@ IRC_LISTEN_ADDR: ""
|
|||||||
set only over the HTTP API.
|
set only over the HTTP API.
|
||||||
- **User modes**: `+o` (operator, set only via `OPER`), `+w` (receives
|
- **User modes**: `+o` (operator, set only via `OPER`), `+w` (receives
|
||||||
`WALLOPS`). `MODE` for any nick other than your own is rejected with
|
`WALLOPS`). `MODE` for any nick other than your own is rejected with
|
||||||
`ERR_USERSDONTMATCH` (502), for both queries and changes. Nick comparison is
|
`ERR_USERSDONTMATCH` (502), for both queries and changes.
|
||||||
case-insensitive.
|
|
||||||
- **KILL**: an operator's `KILL` broadcasts the victim's `QUIT` to its channel
|
- **KILL**: an operator's `KILL` broadcasts the victim's `QUIT` to its channel
|
||||||
peers, deletes its session, and then sends the victim a `KILL` and
|
peers, deletes its session, and then sends the victim a `KILL` and
|
||||||
`ERROR :Closing Link` before closing its socket. This applies to victims on
|
`ERROR :Closing Link` before closing its socket. This applies to victims on
|
||||||
@@ -2701,12 +2700,6 @@ API reaches IRC listener members as an IRC `MODE` line with its value as a
|
|||||||
separate parameter, e.g. `MODE #general +k secretpass` or
|
separate parameter, e.g. `MODE #general +k secretpass` or
|
||||||
`MODE #general +o bob`.
|
`MODE #general +o bob`.
|
||||||
|
|
||||||
One gap remains: the IRC listener does not pass on a `PRIVMSG`, `NOTICE`,
|
|
||||||
`JOIN`, `PART`, `NICK` or `QUIT` whose sender's nick matches the user's own nick
|
|
||||||
ignoring case, so an IRC listener user `alice` never sees these from `Alice`,
|
|
||||||
although nicks are case-sensitive. https://git.eeqj.de/sneak/neoirc/issues/128
|
|
||||||
tracks it.
|
|
||||||
|
|
||||||
### Docker Usage
|
### Docker Usage
|
||||||
|
|
||||||
To expose the IRC port in Docker (the listener is enabled by default on
|
To expose the IRC port in Docker (the listener is enabled by default on
|
||||||
|
|||||||
@@ -669,6 +669,9 @@ func TestCreateSessionDuplicate(t *testing.T) {
|
|||||||
if resp.StatusCode != http.StatusConflict {
|
if resp.StatusCode != http.StatusConflict {
|
||||||
t.Fatalf("expected 409, got %d", resp.StatusCode)
|
t.Fatalf("expected 409, got %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nicks are case-sensitive: Alice is not alice.
|
||||||
|
tserver.createSession("Alice")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEachTestServerHasItsOwnDatabase checks that starting a
|
// TestEachTestServerHasItsOwnDatabase checks that starting a
|
||||||
|
|||||||
@@ -254,8 +254,7 @@ func (hdlr *Handlers) handleWallops(
|
|||||||
|
|
||||||
// handleUserMode handles MODE for a nick: a query when the
|
// handleUserMode handles MODE for a nick: a query when the
|
||||||
// body is empty, otherwise a change by the mode string in
|
// body is empty, otherwise a change by the mode string in
|
||||||
// the body. Only the caller's own nick, in any letter case,
|
// the body. Only the caller's own nick is allowed.
|
||||||
// is allowed.
|
|
||||||
func (hdlr *Handlers) handleUserMode(
|
func (hdlr *Handlers) handleUserMode(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
@@ -263,7 +262,7 @@ func (hdlr *Handlers) handleUserMode(
|
|||||||
nick, target string,
|
nick, target string,
|
||||||
bodyLines func() []string,
|
bodyLines func() []string,
|
||||||
) {
|
) {
|
||||||
if !strings.EqualFold(target, nick) {
|
if target != nick {
|
||||||
hdlr.respondIRCError(
|
hdlr.respondIRCError(
|
||||||
writer, request, clientID, sessionID,
|
writer, request, clientID, sessionID,
|
||||||
irc.ErrUsersDoNotMatch, nick, nil,
|
irc.ErrUsersDoNotMatch, nick, nil,
|
||||||
|
|||||||
@@ -290,8 +290,10 @@ func TestUserMode(t *testing.T) {
|
|||||||
target, modeStr, numeric, modes string
|
target, modeStr, numeric, modes string
|
||||||
}{
|
}{
|
||||||
{nick, "+w", "221", "+w"},
|
{nick, "+w", "221", "+w"},
|
||||||
|
{"Alice", "", "502", ""},
|
||||||
|
{"Alice", "-w", "502", ""},
|
||||||
{nick, "", "221", "+w"},
|
{nick, "", "221", "+w"},
|
||||||
{strings.ToUpper(nick), "-w", "221", "+"},
|
{nick, "-w", "221", "+"},
|
||||||
{nick, "+z", "501", ""},
|
{nick, "+z", "501", ""},
|
||||||
{nick, "+o", "501", ""},
|
{nick, "+o", "501", ""},
|
||||||
{other, "", "502", ""},
|
{other, "", "502", ""},
|
||||||
|
|||||||
@@ -710,12 +710,12 @@ func (c *Conn) applyChannelModes(
|
|||||||
|
|
||||||
// handleUserMode handles MODE for a nick: a query without a
|
// handleUserMode handles MODE for a nick: a query without a
|
||||||
// mode string, otherwise a change. Only the client's own
|
// mode string, otherwise a change. Only the client's own
|
||||||
// nick, in any letter case, is allowed.
|
// nick is allowed.
|
||||||
func (c *Conn) handleUserMode(
|
func (c *Conn) handleUserMode(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msg *Message,
|
msg *Message,
|
||||||
) {
|
) {
|
||||||
if !strings.EqualFold(msg.Params[0], c.currentNick()) {
|
if msg.Params[0] != c.currentNick() {
|
||||||
c.sendNumeric(
|
c.sendNumeric(
|
||||||
irc.ErrUsersDoNotMatch,
|
irc.ErrUsersDoNotMatch,
|
||||||
"Can't change mode for other users",
|
"Can't change mode for other users",
|
||||||
|
|||||||
@@ -1084,9 +1084,9 @@ func TestIntegrationWallops(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestIntegrationUserMode covers MODE for a nick: changes
|
// TestIntegrationUserMode covers MODE for a nick: changes
|
||||||
// and queries of your own modes in any letter case, refusal
|
// and queries of your own modes, refusal for another nick,
|
||||||
// for another nick, and a rejected mode string changing
|
// even one that differs from yours only in letter case, and
|
||||||
// nothing.
|
// a rejected mode string changing nothing.
|
||||||
func TestIntegrationUserMode(t *testing.T) {
|
func TestIntegrationUserMode(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -1106,7 +1106,9 @@ func TestIntegrationUserMode(t *testing.T) {
|
|||||||
for _, step := range []struct {
|
for _, step := range []struct {
|
||||||
line, want string
|
line, want string
|
||||||
}{
|
}{
|
||||||
{"MODE ALICE +w", " 221 alice +w"},
|
{"MODE alice +w", " 221 alice +w"},
|
||||||
|
{"MODE Alice", notYours},
|
||||||
|
{"MODE Alice -w", notYours},
|
||||||
{"MODE bob", notYours},
|
{"MODE bob", notYours},
|
||||||
{"MODE bob -w", notYours},
|
{"MODE bob -w", notYours},
|
||||||
{"MODE alice xw", unknown},
|
{"MODE alice xw", unknown},
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ func (c *Conn) deliverTextMessage(
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
// Don't echo our own messages back.
|
// Don't echo our own messages back.
|
||||||
if strings.EqualFold(from, nick) {
|
if from == nick {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ func (c *Conn) deliverJoin(msg *db.IRCMessage) {
|
|||||||
|
|
||||||
// Don't echo our own JOINs (we already sent them
|
// Don't echo our own JOINs (we already sent them
|
||||||
// during joinChannel).
|
// during joinChannel).
|
||||||
if strings.EqualFold(msg.From, nick) {
|
if msg.From == nick {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ func (c *Conn) deliverPart(msg *db.IRCMessage, text string) {
|
|||||||
nick := c.nick
|
nick := c.nick
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if strings.EqualFold(msg.From, nick) {
|
if msg.From == nick {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ func (c *Conn) deliverNickChange(
|
|||||||
nick := c.nick
|
nick := c.nick
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if strings.EqualFold(msg.From, nick) {
|
if msg.From == nick {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ func (c *Conn) deliverQuitMsg(
|
|||||||
nick := c.nick
|
nick := c.nick
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if strings.EqualFold(msg.From, nick) {
|
if msg.From == nick {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,3 +185,39 @@ func TestRelayLineBreaks(t *testing.T) {
|
|||||||
t.Errorf("got %q, want %q", got, want)
|
t.Errorf("got %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRelayNickDiffersOnlyInCase checks that an IRC listener
|
||||||
|
// user alice receives a JOIN, PRIVMSG, PART, NICK and QUIT
|
||||||
|
// from Alice in a channel they share: nicks are
|
||||||
|
// case-sensitive, so Alice is someone else.
|
||||||
|
func TestRelayNickDiffersOnlyInCase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
env := newTestEnv(t)
|
||||||
|
|
||||||
|
alice := env.dial(t)
|
||||||
|
alice.register("alice")
|
||||||
|
alice.joinAndDrain("#case")
|
||||||
|
|
||||||
|
capitalAlice := env.dial(t)
|
||||||
|
capitalAlice.register("Alice")
|
||||||
|
|
||||||
|
for _, step := range []struct {
|
||||||
|
line, want string
|
||||||
|
}{
|
||||||
|
{"JOIN #case", ":Alice!Alice@* JOIN #case"},
|
||||||
|
{"PRIVMSG #case :hi there", ":Alice!Alice@* PRIVMSG #case :hi there"},
|
||||||
|
{"PART #case :see you", ":Alice!Alice@* PART #case :see you"},
|
||||||
|
{"JOIN #case", ":Alice!Alice@* JOIN #case"},
|
||||||
|
{"NICK Alicia", ":Alice!Alice@* NICK Alicia"},
|
||||||
|
{"NICK Alice", ":Alicia!Alicia@* NICK Alice"}, // back for QUIT
|
||||||
|
{"QUIT", ":Alice!Alice@* QUIT :Quit: Connection closed"},
|
||||||
|
} {
|
||||||
|
capitalAlice.send(step.line)
|
||||||
|
|
||||||
|
got := alice.readLine()
|
||||||
|
if got != step.want {
|
||||||
|
t.Errorf("%s: got %q, want %q", step.line, got, step.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user