imapkit 0.0.0-stage → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +16 -0
- package/README.md +608 -2
- package/bin/help.txt +98 -0
- package/bin/imapkit.js +108 -0
- package/cert/server.crt +20 -0
- package/cert/server.key +28 -0
- package/lib/addressparser.js +283 -0
- package/lib/arguments.js +112 -0
- package/lib/bodystructure.js +149 -0
- package/lib/command-states.js +109 -0
- package/lib/commands/append.js +313 -0
- package/lib/commands/capability.js +47 -0
- package/lib/commands/check.js +21 -0
- package/lib/commands/close.js +30 -0
- package/lib/commands/copy.js +115 -0
- package/lib/commands/create.js +52 -0
- package/lib/commands/delete.js +64 -0
- package/lib/commands/examine.js +7 -0
- package/lib/commands/expunge.js +27 -0
- package/lib/commands/fetch.js +229 -0
- package/lib/commands/handlers/fetch.js +209 -0
- package/lib/commands/handlers/flags.js +42 -0
- package/lib/commands/handlers/search.js +519 -0
- package/lib/commands/handlers/status.js +85 -0
- package/lib/commands/handlers/store.js +127 -0
- package/lib/commands/list.js +100 -0
- package/lib/commands/login.js +67 -0
- package/lib/commands/logout.js +41 -0
- package/lib/commands/lsub.js +87 -0
- package/lib/commands/noop.js +21 -0
- package/lib/commands/rename.js +102 -0
- package/lib/commands/search.js +76 -0
- package/lib/commands/select.js +289 -0
- package/lib/commands/status.js +63 -0
- package/lib/commands/store.js +151 -0
- package/lib/commands/subscribe.js +53 -0
- package/lib/commands/uid copy.js +7 -0
- package/lib/commands/uid fetch.js +5 -0
- package/lib/commands/uid search.js +5 -0
- package/lib/commands/uid store.js +5 -0
- package/lib/commands/unsubscribe.js +50 -0
- package/lib/dates.js +123 -0
- package/lib/deflate-layer.js +232 -0
- package/lib/envelope.js +82 -0
- package/lib/esearch.js +208 -0
- package/lib/framing.js +102 -0
- package/lib/list-extensions.js +36 -0
- package/lib/load-plugins.js +109 -0
- package/lib/mailbox-name.js +133 -0
- package/lib/mimeparser.js +778 -0
- package/lib/mock-client.js +233 -0
- package/lib/numbers.js +52 -0
- package/lib/plugins/acl.js +964 -0
- package/lib/plugins/appendlimit.js +83 -0
- package/lib/plugins/auth-plain.js +94 -0
- package/lib/plugins/binary.js +256 -0
- package/lib/plugins/catenate.js +253 -0
- package/lib/plugins/compress.js +76 -0
- package/lib/plugins/condstore.js +563 -0
- package/lib/plugins/context-search.js +321 -0
- package/lib/plugins/context-sort.js +19 -0
- package/lib/plugins/create-special-use.js +108 -0
- package/lib/plugins/enable.js +155 -0
- package/lib/plugins/esearch.js +156 -0
- package/lib/plugins/esort.js +60 -0
- package/lib/plugins/id.js +138 -0
- package/lib/plugins/idle.js +105 -0
- package/lib/plugins/imap4rev2.js +202 -0
- package/lib/plugins/list-extended.js +258 -0
- package/lib/plugins/list-status.js +31 -0
- package/lib/plugins/literalminus.js +20 -0
- package/lib/plugins/literalplus.js +18 -0
- package/lib/plugins/logindisabled.js +50 -0
- package/lib/plugins/messagelimit.js +234 -0
- package/lib/plugins/metadata-server.js +13 -0
- package/lib/plugins/metadata.js +475 -0
- package/lib/plugins/move.js +110 -0
- package/lib/plugins/multiappend.js +26 -0
- package/lib/plugins/multisearch.js +269 -0
- package/lib/plugins/namespace.js +67 -0
- package/lib/plugins/notify.js +654 -0
- package/lib/plugins/oauthbearer.js +217 -0
- package/lib/plugins/objectid.js +243 -0
- package/lib/plugins/partial.js +68 -0
- package/lib/plugins/preview.js +400 -0
- package/lib/plugins/qresync.js +525 -0
- package/lib/plugins/quota.js +285 -0
- package/lib/plugins/replace.js +145 -0
- package/lib/plugins/sasl-ir.js +12 -0
- package/lib/plugins/savedate.js +59 -0
- package/lib/plugins/savelimit.js +18 -0
- package/lib/plugins/searchres.js +82 -0
- package/lib/plugins/sort-display.js +23 -0
- package/lib/plugins/sort.js +132 -0
- package/lib/plugins/special-use.js +95 -0
- package/lib/plugins/starttls.js +57 -0
- package/lib/plugins/status-size.js +19 -0
- package/lib/plugins/thread-orderedsubject.js +16 -0
- package/lib/plugins/thread-references.js +16 -0
- package/lib/plugins/uidonly.js +135 -0
- package/lib/plugins/uidplus.js +124 -0
- package/lib/plugins/unauthenticate.js +28 -0
- package/lib/plugins/unselect.js +36 -0
- package/lib/plugins/utf8-accept.js +68 -0
- package/lib/plugins/x-gm-ext-1.js +456 -0
- package/lib/plugins/xoauth2.js +188 -0
- package/lib/plugins/xtoybird.js +282 -0
- package/lib/server.js +2880 -0
- package/lib/smtp-listener.js +51 -0
- package/lib/sorting.js +373 -0
- package/lib/threading.js +357 -0
- package/lib/utf8-session.js +123 -0
- package/lib/vanished.js +57 -0
- package/package.json +61 -5
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// https://developers.google.com/gmail/xoauth2_protocol
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @help Enables XOAUTH2 capability
|
|
7
|
+
* @help Implementation difference - ImapKit requires
|
|
8
|
+
* @help SASL-IR enabled and used while Gmail does not
|
|
9
|
+
* @help Valid login info:
|
|
10
|
+
* @help Username: testuser
|
|
11
|
+
* @help Access Token: testtoken
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const utf8 = new TextDecoder('utf-8', { fatal: true });
|
|
15
|
+
|
|
16
|
+
module.exports = function (server) {
|
|
17
|
+
// Register capability, usable for non authenticated users
|
|
18
|
+
server.registerCapability('AUTH=XOAUTH2', connection => {
|
|
19
|
+
return connection.state === 'Not Authenticated';
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.setCommandHandler('AUTHENTICATE XOAUTH2', (connection, parsed, data, callback) => {
|
|
23
|
+
if (!server.capabilities['SASL-IR'] || !server.capabilities['SASL-IR'](connection)) {
|
|
24
|
+
connection.send(
|
|
25
|
+
{
|
|
26
|
+
tag: parsed.tag,
|
|
27
|
+
command: 'BAD',
|
|
28
|
+
attributes: [
|
|
29
|
+
{
|
|
30
|
+
type: 'TEXT',
|
|
31
|
+
value: 'SASL-IR must be enabled to support XOAUTH2'
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
'AUTHENTICATE XOAUTH2 FAILED',
|
|
36
|
+
parsed,
|
|
37
|
+
data
|
|
38
|
+
);
|
|
39
|
+
return callback();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!parsed.attributes || parsed.attributes.length !== 1 || !parsed.attributes[0] || ['STRING', 'ATOM'].indexOf(parsed.attributes[0].type) < 0) {
|
|
43
|
+
connection.send(
|
|
44
|
+
{
|
|
45
|
+
tag: parsed.tag,
|
|
46
|
+
command: 'NO',
|
|
47
|
+
attributes: [
|
|
48
|
+
{
|
|
49
|
+
type: 'TEXT',
|
|
50
|
+
value: 'Invalid SASL argument'
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
'AUTHENTICATE XOAUTH2 FAILED',
|
|
55
|
+
parsed,
|
|
56
|
+
data
|
|
57
|
+
);
|
|
58
|
+
return callback();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const decoded = connection.decodeSaslResponse(parsed.attributes[0].value);
|
|
62
|
+
if (!decoded) {
|
|
63
|
+
connection.sendStatus(parsed, data, 'BAD', 'Invalid base64 in SASL response', false, 'AUTHENTICATE XOAUTH2 FAILED');
|
|
64
|
+
return callback();
|
|
65
|
+
}
|
|
66
|
+
let parts = [];
|
|
67
|
+
try {
|
|
68
|
+
// user names are unicode strings, like the keys of `users`
|
|
69
|
+
parts = utf8.decode(decoded).split('\x01');
|
|
70
|
+
} catch {
|
|
71
|
+
// invalid UTF-8 is a malformed payload like any other
|
|
72
|
+
}
|
|
73
|
+
const user = (parts[0] || '').substr(5);
|
|
74
|
+
const accessToken = (parts[1] || '').substr(12);
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
parts.length !== 4 ||
|
|
78
|
+
!parts[0].match(/^user=/) ||
|
|
79
|
+
!parts[1].match(/^auth=Bearer /) ||
|
|
80
|
+
!user || // Must be present
|
|
81
|
+
!accessToken || // Must be present
|
|
82
|
+
parts[2] || // Must be empty
|
|
83
|
+
parts[3] // Must be empty
|
|
84
|
+
) {
|
|
85
|
+
connection.send(
|
|
86
|
+
{
|
|
87
|
+
tag: parsed.tag,
|
|
88
|
+
command: 'NO',
|
|
89
|
+
attributes: [
|
|
90
|
+
{
|
|
91
|
+
type: 'TEXT',
|
|
92
|
+
value: 'Invalid SASL argument.'
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
'AUTHENTICATE XOAUTH2 FAILED',
|
|
97
|
+
parsed,
|
|
98
|
+
data
|
|
99
|
+
);
|
|
100
|
+
return callback();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const userData = connection.server.getUser(user);
|
|
104
|
+
if (!userData) {
|
|
105
|
+
connection.send(
|
|
106
|
+
{
|
|
107
|
+
tag: parsed.tag,
|
|
108
|
+
command: 'NO',
|
|
109
|
+
attributes: [
|
|
110
|
+
// RFC 5530 section 3: unknown user or bad token
|
|
111
|
+
{ type: 'SECTION', section: [{ type: 'ATOM', value: 'AUTHENTICATIONFAILED' }] },
|
|
112
|
+
{
|
|
113
|
+
type: 'TEXT',
|
|
114
|
+
value: 'Invalid credentials'
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
'AUTHENTICATE XOAUTH2 FAILED',
|
|
119
|
+
parsed,
|
|
120
|
+
data
|
|
121
|
+
);
|
|
122
|
+
return callback();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!userData.xoauth2 || userData.xoauth2.accessToken !== accessToken) {
|
|
126
|
+
connection.send(
|
|
127
|
+
{
|
|
128
|
+
tag: '+',
|
|
129
|
+
attributes: [
|
|
130
|
+
{
|
|
131
|
+
type: 'ATOM',
|
|
132
|
+
value: Buffer.from(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
status: '400',
|
|
135
|
+
schemes: 'Bearer',
|
|
136
|
+
scope: 'https://mail.google.com/'
|
|
137
|
+
})
|
|
138
|
+
).toString('base64')
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
'AUTHENTICATE XOAUTH2 FAILED',
|
|
143
|
+
parsed,
|
|
144
|
+
data
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// wait for response
|
|
148
|
+
connection.inputHandler = function () {
|
|
149
|
+
connection.inputHandler = false;
|
|
150
|
+
connection.send(
|
|
151
|
+
{
|
|
152
|
+
tag: parsed.tag,
|
|
153
|
+
command: 'NO',
|
|
154
|
+
attributes: [
|
|
155
|
+
{ type: 'SECTION', section: [{ type: 'ATOM', value: 'AUTHENTICATIONFAILED' }] },
|
|
156
|
+
{
|
|
157
|
+
type: 'TEXT',
|
|
158
|
+
value: 'SASL authentication failed'
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
'AUTHENTICATE XOAUTH2 FAILED',
|
|
163
|
+
parsed,
|
|
164
|
+
data
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
} else {
|
|
168
|
+
connection.state = 'Authenticated';
|
|
169
|
+
connection.username = user;
|
|
170
|
+
connection.send(
|
|
171
|
+
{
|
|
172
|
+
tag: parsed.tag,
|
|
173
|
+
command: 'OK',
|
|
174
|
+
attributes: [
|
|
175
|
+
{
|
|
176
|
+
type: 'TEXT',
|
|
177
|
+
value: 'User logged in'
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
'AUTHENTICATE XOAUTH2 SUCCESS',
|
|
182
|
+
parsed,
|
|
183
|
+
data
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return callback();
|
|
187
|
+
});
|
|
188
|
+
};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { states } = require('../command-states');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @help Custom plugin to allow programmatic control of the server
|
|
7
|
+
* @help Available commands:
|
|
8
|
+
* @help XTOYBIRD SERVER dumps server internals
|
|
9
|
+
* @help XTOYBIRD CONNECTION dumps connection internals
|
|
10
|
+
* @help XTOYBIRD STORAGE dumps storage as JSON
|
|
11
|
+
* @help XTOYBIRD USERADD "username" "password" adds or updates user
|
|
12
|
+
* @help XTOYBIRD USERDEL "username" removes an user
|
|
13
|
+
* @help XTOYBIRD SHUTDOWN Closes the server after the last client
|
|
14
|
+
* @help disconnects. New connections are rejected.
|
|
15
|
+
* @help Commands are only allowed after login, and only for the owner
|
|
16
|
+
* @help (aclOwner option) when ACL is loaded. A test control plugin,
|
|
17
|
+
* @help any user it allows can read and change everything
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const util = require('util');
|
|
21
|
+
const { isAstring } = require('../arguments');
|
|
22
|
+
|
|
23
|
+
module.exports = function (server) {
|
|
24
|
+
let username;
|
|
25
|
+
let password;
|
|
26
|
+
let message;
|
|
27
|
+
|
|
28
|
+
const sendError = (connection, parsed, data, callback, command, message) => {
|
|
29
|
+
connection.sendStatus(parsed, data, command, message, false, 'INVALID COMMAND');
|
|
30
|
+
return callback();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
server.setCommandHandler(
|
|
34
|
+
'XTOYBIRD',
|
|
35
|
+
(connection, parsed, data, callback) => {
|
|
36
|
+
// XTOYBIRD bypasses every access check (STORAGE dumps all mailboxes), so with ACL only the owner may use it
|
|
37
|
+
if (server.acl && !server.acl.isOwner(connection)) {
|
|
38
|
+
connection.sendStatus(parsed, data, 'NO', 'XTOYBIRD is only allowed for the owner', 'NOPERM', 'INVALID COMMAND');
|
|
39
|
+
return callback();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (parsed.attributes) {
|
|
43
|
+
const subcommand = ((parsed.attributes[0] && parsed.attributes[0].value) || '').toString().toUpperCase();
|
|
44
|
+
if (subcommand === 'USERADD' && (parsed.attributes.length !== 3 || !isAstring(parsed.attributes[1]) || !isAstring(parsed.attributes[2]))) {
|
|
45
|
+
return sendError(connection, parsed, data, callback, 'BAD', 'USERADD expects username and password');
|
|
46
|
+
}
|
|
47
|
+
if (subcommand === 'USERDEL' && (parsed.attributes.length !== 2 || !isAstring(parsed.attributes[1]))) {
|
|
48
|
+
return sendError(connection, parsed, data, callback, 'BAD', 'USERDEL expects username');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
switch (subcommand) {
|
|
52
|
+
case 'SERVER':
|
|
53
|
+
connection.send(
|
|
54
|
+
{
|
|
55
|
+
tag: '*',
|
|
56
|
+
command: 'XTOYBIRD',
|
|
57
|
+
attributes: [
|
|
58
|
+
{
|
|
59
|
+
type: 'SECTION',
|
|
60
|
+
section: [
|
|
61
|
+
{
|
|
62
|
+
type: 'ATOM',
|
|
63
|
+
value: 'XDUMPVAL'
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'LITERAL',
|
|
69
|
+
value: util.inspect(server, false, 22)
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
'CONNECTION STATUS',
|
|
74
|
+
parsed,
|
|
75
|
+
data
|
|
76
|
+
);
|
|
77
|
+
break;
|
|
78
|
+
|
|
79
|
+
case 'CONNECTION':
|
|
80
|
+
connection.send(
|
|
81
|
+
{
|
|
82
|
+
tag: '*',
|
|
83
|
+
command: 'XTOYBIRD',
|
|
84
|
+
attributes: [
|
|
85
|
+
{
|
|
86
|
+
type: 'SECTION',
|
|
87
|
+
section: [
|
|
88
|
+
{
|
|
89
|
+
type: 'ATOM',
|
|
90
|
+
value: 'XDUMPVAL'
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'LITERAL',
|
|
96
|
+
value: util.inspect(connection, false, 22)
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
'CONNECTION STATUS',
|
|
101
|
+
parsed,
|
|
102
|
+
data
|
|
103
|
+
);
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'STORAGE':
|
|
107
|
+
connection.send(
|
|
108
|
+
{
|
|
109
|
+
tag: '*',
|
|
110
|
+
command: 'XTOYBIRD',
|
|
111
|
+
attributes: [
|
|
112
|
+
{
|
|
113
|
+
type: 'SECTION',
|
|
114
|
+
section: [
|
|
115
|
+
{
|
|
116
|
+
type: 'ATOM',
|
|
117
|
+
value: 'XJSONDUMP'
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'LITERAL',
|
|
123
|
+
value: JSON.stringify(connection.server.storage, false, 4)
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
'CONNECTION STATUS',
|
|
128
|
+
parsed,
|
|
129
|
+
data
|
|
130
|
+
);
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case 'USERADD':
|
|
134
|
+
username = parsed.attributes[1].value || '';
|
|
135
|
+
password = parsed.attributes[2].value || '';
|
|
136
|
+
|
|
137
|
+
if (connection.server.getUser(username)) {
|
|
138
|
+
connection.server.users[username].password = password;
|
|
139
|
+
message = 'updated';
|
|
140
|
+
} else {
|
|
141
|
+
connection.server.users[username] = {
|
|
142
|
+
password: password
|
|
143
|
+
};
|
|
144
|
+
message = 'added';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
connection.send(
|
|
148
|
+
{
|
|
149
|
+
tag: '*',
|
|
150
|
+
command: 'XTOYBIRD',
|
|
151
|
+
attributes: [
|
|
152
|
+
{
|
|
153
|
+
type: 'SECTION',
|
|
154
|
+
section: [
|
|
155
|
+
{
|
|
156
|
+
type: 'ATOM',
|
|
157
|
+
value: 'XUSER'
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: 'TEXT',
|
|
163
|
+
value: 'User ' + displayName(username) + message + ' successfully'
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
'CONNECTION USERADD',
|
|
168
|
+
parsed,
|
|
169
|
+
data
|
|
170
|
+
);
|
|
171
|
+
break;
|
|
172
|
+
|
|
173
|
+
case 'USERDEL':
|
|
174
|
+
username = parsed.attributes[1].value || '';
|
|
175
|
+
|
|
176
|
+
if (username in connection.server.users) {
|
|
177
|
+
delete connection.server.users[username];
|
|
178
|
+
message = 'succeeded';
|
|
179
|
+
} else {
|
|
180
|
+
message = 'failed (no such user)';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
connection.send(
|
|
184
|
+
{
|
|
185
|
+
tag: '*',
|
|
186
|
+
command: 'XTOYBIRD',
|
|
187
|
+
attributes: [
|
|
188
|
+
{
|
|
189
|
+
type: 'SECTION',
|
|
190
|
+
section: [
|
|
191
|
+
{
|
|
192
|
+
type: 'ATOM',
|
|
193
|
+
value: 'XUSER'
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: 'TEXT',
|
|
199
|
+
value: 'Removing user ' + displayName(username) + message
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
},
|
|
203
|
+
'CONNECTION USERDEL',
|
|
204
|
+
parsed,
|
|
205
|
+
data
|
|
206
|
+
);
|
|
207
|
+
break;
|
|
208
|
+
|
|
209
|
+
case 'SHUTDOWN':
|
|
210
|
+
connection.send(
|
|
211
|
+
{
|
|
212
|
+
tag: '*',
|
|
213
|
+
command: 'OK',
|
|
214
|
+
attributes: [
|
|
215
|
+
{
|
|
216
|
+
type: 'SECTION',
|
|
217
|
+
section: [
|
|
218
|
+
{
|
|
219
|
+
type: 'ATOM',
|
|
220
|
+
value: 'ALERT'
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: 'TEXT',
|
|
226
|
+
value: 'System scheduled for shutdown'
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
'CONNECTION SHUTDOWN',
|
|
231
|
+
parsed,
|
|
232
|
+
data
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
connection.server.close();
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
default:
|
|
239
|
+
connection.send(
|
|
240
|
+
{
|
|
241
|
+
tag: parsed.tag,
|
|
242
|
+
command: 'BAD',
|
|
243
|
+
attributes: [
|
|
244
|
+
{
|
|
245
|
+
type: 'TEXT',
|
|
246
|
+
value: 'Unknown command'
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
'INVALID COMMAND',
|
|
251
|
+
parsed,
|
|
252
|
+
data
|
|
253
|
+
);
|
|
254
|
+
return callback();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
connection.send(
|
|
259
|
+
{
|
|
260
|
+
tag: parsed.tag,
|
|
261
|
+
command: 'OK',
|
|
262
|
+
attributes: [
|
|
263
|
+
{
|
|
264
|
+
type: 'TEXT',
|
|
265
|
+
value: 'XTOYBIRD Completed'
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
'XTOYBIRD',
|
|
270
|
+
parsed,
|
|
271
|
+
data
|
|
272
|
+
);
|
|
273
|
+
return callback();
|
|
274
|
+
},
|
|
275
|
+
{ states: states.AUTHENTICATED }
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// Response text is free text that some clients still tokenize, so only plain user names are echoed
|
|
280
|
+
function displayName(username) {
|
|
281
|
+
return /^[\w.@+-]+$/.test(username) ? "'" + username + "' " : '';
|
|
282
|
+
}
|