Skip to content

Commit 7c9dbee

Browse files
authored
Allow usernames to include more characters when using the !username command (#1719)
* Allow usernames to include more characters when using the `!username` command. * Rename 1718.bugfix to 1719.bugfix * Ban other non-printable ASCII characters * Fix lint * Fix lint again * I can't read
1 parent f7c907e commit 7c9dbee

File tree

3 files changed

+47
-5
lines changed

3 files changed

+47
-5
lines changed

changelog.d/1719.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow usernames to include more characters when using the `!username` command.

spec/integ/admin-rooms.spec.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,7 @@ describe("Admin rooms", function() {
12701270
// Ensure that the user reconnects
12711271
await env.mockAppService._trigger("type:m.room.message", {
12721272
content: {
1273-
body: "!username foobar",
1273+
body: "!username foobar\"[]{}`_^",
12741274
msgtype: "m.text"
12751275
},
12761276
sender: userId,
@@ -1279,7 +1279,36 @@ describe("Admin rooms", function() {
12791279
});
12801280
await sendPromise;
12811281
const userCfg = await env.ircBridge.getStore().getIrcClientConfig(userId, roomMapping.server);
1282-
expect(userCfg.getUsername()).toEqual("foobar");
1282+
expect(userCfg.getUsername()).toEqual("foobar\"[]{}`_^");
1283+
});
1284+
1285+
it("should not be able to store an invalid username with !username", async function() {
1286+
const sdk = env.clientMock._client(botUserId);
1287+
1288+
const sendPromise = sdk.sendEvent.and.callFake(async (roomId, _, content) => {
1289+
expect(roomId).toEqual(adminRoomId);
1290+
expect(content.msgtype).toEqual("m.notice");
1291+
expect(content.body).toEqual(
1292+
"Username contained invalid characters not supported by IRC (\"\\u0000\")."
1293+
);
1294+
return {};
1295+
});
1296+
1297+
let userCfg = await env.ircBridge.getStore().getIrcClientConfig(userId, roomMapping.server);
1298+
const defaultUsername = userCfg.getUsername();
1299+
1300+
await env.mockAppService._trigger("type:m.room.message", {
1301+
content: {
1302+
body: "!username foo\0bar",
1303+
msgtype: "m.text"
1304+
},
1305+
sender: userId,
1306+
room_id: adminRoomId,
1307+
type: "m.room.message"
1308+
});
1309+
await sendPromise;
1310+
userCfg = await env.ircBridge.getStore().getIrcClientConfig(userId, roomMapping.server);
1311+
expect(userCfg.getUsername()).toEqual(defaultUsername); // unchanged
12831312
});
12841313

12851314
it("should be able to store a password with !storepass", async function() {

src/bridge/AdminRoomHandler.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { MatrixHandler, MatrixSimpleMessage } from "./MatrixHandler";
2525
import logging from "../logging";
2626
import * as RoomCreation from "./RoomCreation";
2727
import { getBridgeVersion } from "matrix-appservice-bridge";
28-
import { IdentGenerator } from "../irc/IdentGenerator";
2928
import { Provisioner } from "../provisioning/Provisioner";
3029
import { IrcProvisioningError } from "../provisioning/Schema";
3130

@@ -39,6 +38,17 @@ enum CommandPermission {
3938
// This is just a length to avoid silly long usernames
4039
const SANE_USERNAME_LENGTH = 64;
4140

41+
// Technically, anything but \0 is allowed as username (aka. authcid and authzid):
42+
// https://www.rfc-editor.org/rfc/rfc4616#section-2
43+
//
44+
// However, IRC services are very unlikely to allow the username to contain CR (0x0A)
45+
// or LF (0x0D) because they would not fit in the wire format or spaces (0x20) because
46+
// usernames are usually followed by passwords in "PRIVMSG NickServ :REGISTER" commands
47+
// and IRCv3 draft/account-registration.
48+
// Since we are at it, we might as well ban other non-printable ASCII characters
49+
// (0x00 to 0x1F, plus DEL (0x7F)), as they are most likely mistakes.
50+
const SASL_USERNAME_INVALID_CHARS_PATTERN = /[\x00-\x20\x7F]+/; // eslint-disable-line
51+
4252
interface Command {
4353
example: string;
4454
summary: string;
@@ -490,6 +500,7 @@ export class AdminRoomHandler {
490500
try {
491501
// Allow passwords with spaces
492502
const username = args[0]?.trim();
503+
const invalidChars = SASL_USERNAME_INVALID_CHARS_PATTERN.exec(username);
493504
if (!username) {
494505
notice = new MatrixAction(
495506
ActionType.Notice,
@@ -503,10 +514,11 @@ export class AdminRoomHandler {
503514
`Username is longer than the maximum permitted by the bridge (${SANE_USERNAME_LENGTH}).`
504515
);
505516
}
506-
else if (IdentGenerator.sanitiseUsername(username) !== username) {
517+
else if (invalidChars !== null) {
507518
notice = new MatrixAction(
508519
ActionType.Notice,
509-
`Username contained invalid characters not supported by IRC.`
520+
"Username contained invalid characters not supported by IRC " +
521+
`(${JSON.stringify(invalidChars.join(""))}).`
510522
);
511523
}
512524
else {

0 commit comments

Comments
 (0)