drachtio-srf 5.0.21 → 5.0.23
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/lib/digest-client.js +12 -4
- package/lib/drachtio-agent.js +31 -4
- package/package.json +1 -1
- package/test/unit-tests/digest-client.js +63 -0
package/lib/digest-client.js
CHANGED
|
@@ -42,7 +42,12 @@ module.exports = class DigestClient {
|
|
|
42
42
|
if (!this.res.has(header)) {
|
|
43
43
|
return callback(new Error(`missing ${header} in ${this.res.statusCode} response`));
|
|
44
44
|
}
|
|
45
|
-
const
|
|
45
|
+
const rawChallenge = this.res.get(header);
|
|
46
|
+
if (typeof rawChallenge !== 'string' || rawChallenge.length === 0) {
|
|
47
|
+
return callback(new Error(
|
|
48
|
+
`empty or non-string ${header} value in ${this.res.statusCode} response`));
|
|
49
|
+
}
|
|
50
|
+
const challenge = this._parseChallenge(rawChallenge);
|
|
46
51
|
|
|
47
52
|
const ha1 = crypto.createHash('md5');
|
|
48
53
|
ha1.update([username, challenge.realm, password].join(':'));
|
|
@@ -155,13 +160,16 @@ module.exports = class DigestClient {
|
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
_parseChallenge(digest) {
|
|
163
|
+
const params = {};
|
|
164
|
+
if (typeof digest !== 'string') return params;
|
|
158
165
|
const prefix = 'Digest ';
|
|
159
|
-
const
|
|
166
|
+
const prefixIdx = digest.indexOf(prefix);
|
|
167
|
+
if (prefixIdx === -1) return params;
|
|
168
|
+
const challenge = digest.substr(prefixIdx + prefix.length);
|
|
160
169
|
const parts = challenge.split(',');
|
|
161
170
|
const length = parts.length;
|
|
162
|
-
const params = {};
|
|
163
171
|
for (let i = 0; i < length; i++) {
|
|
164
|
-
const part = parts[i].match(/^\s*?([a-zA-Z0-
|
|
172
|
+
const part = parts[i].match(/^\s*?([a-zA-Z0-9]+)="?(.*?)"?\s*?$/);
|
|
165
173
|
if (part && part.length > 2) {
|
|
166
174
|
params[part[1]] = part[2];
|
|
167
175
|
}
|
package/lib/drachtio-agent.js
CHANGED
|
@@ -301,7 +301,14 @@ class DrachtioAgent extends Emitter {
|
|
|
301
301
|
transactionId: transactionId
|
|
302
302
|
};
|
|
303
303
|
|
|
304
|
-
|
|
304
|
+
let sipMsg;
|
|
305
|
+
try {
|
|
306
|
+
sipMsg = new SipMessage(msg);
|
|
307
|
+
} catch(err) {
|
|
308
|
+
console.error(err, `unable to parse echoed sent request: ${msg}`);
|
|
309
|
+
return params.callback(err);
|
|
310
|
+
}
|
|
311
|
+
const req = new Request(sipMsg, meta);
|
|
305
312
|
req.agent = this;
|
|
306
313
|
req.socket = obj.socket;
|
|
307
314
|
if (params.options.auth) {
|
|
@@ -356,7 +363,14 @@ class DrachtioAgent extends Emitter {
|
|
|
356
363
|
obj.pendingRequests.set(msgId, (token, msg, meta) => {
|
|
357
364
|
obj.pendingRequests.delete(msgId);
|
|
358
365
|
if ('OK' !== token[0]) { if (callback) return callback(token[1]); return; }
|
|
359
|
-
|
|
366
|
+
let responseMsg;
|
|
367
|
+
try {
|
|
368
|
+
responseMsg = new SipMessage(msg);
|
|
369
|
+
} catch(err) {
|
|
370
|
+
console.error(err, `unable to parse echoed sent response: ${msg}`);
|
|
371
|
+
if (callback) callback(err);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
360
374
|
res.meta = meta;
|
|
361
375
|
if (callback) {
|
|
362
376
|
callback(null, responseMsg);
|
|
@@ -401,7 +415,14 @@ class DrachtioAgent extends Emitter {
|
|
|
401
415
|
if ('OK' !== token[0]) {
|
|
402
416
|
return callback(token[1]);
|
|
403
417
|
}
|
|
404
|
-
|
|
418
|
+
let sipMsg;
|
|
419
|
+
try {
|
|
420
|
+
sipMsg = new SipMessage(msg);
|
|
421
|
+
} catch(err) {
|
|
422
|
+
console.error(err, `unable to parse echoed sent ACK: ${msg}`);
|
|
423
|
+
return callback(err);
|
|
424
|
+
}
|
|
425
|
+
callback(null, sipMsg);
|
|
405
426
|
});
|
|
406
427
|
}
|
|
407
428
|
}
|
|
@@ -808,7 +829,13 @@ class DrachtioAgent extends Emitter {
|
|
|
808
829
|
const msgSource = token[2];
|
|
809
830
|
const msgTime = token[3];
|
|
810
831
|
rawMsg = msg.slice(pos + 2);
|
|
811
|
-
|
|
832
|
+
let cdrSipMsg;
|
|
833
|
+
try {
|
|
834
|
+
cdrSipMsg = new SipMessage(rawMsg);
|
|
835
|
+
} catch(err) {
|
|
836
|
+
console.error(err, `unable to parse CDR sip message: ${rawMsg}`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
812
839
|
const args = [msgSource, msgTime];
|
|
813
840
|
if (cdrEvent !== 'attempt') { args.push(token[4]); }
|
|
814
841
|
args.push(cdrSipMsg);
|
package/package.json
CHANGED
|
@@ -134,3 +134,66 @@ describe('DigestClient proxy pinning after challenge', function() {
|
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
});
|
|
137
|
+
|
|
138
|
+
describe('DigestClient malformed challenge handling', function() {
|
|
139
|
+
|
|
140
|
+
function resWithChallenge(value) {
|
|
141
|
+
const options = {
|
|
142
|
+
method: 'INVITE',
|
|
143
|
+
uri: 'sip:u@sbc.example.com',
|
|
144
|
+
auth: {username: 'u', password: 'p'},
|
|
145
|
+
headers: {}
|
|
146
|
+
};
|
|
147
|
+
const req = {
|
|
148
|
+
method: 'INVITE',
|
|
149
|
+
_originalParams: {options},
|
|
150
|
+
get: () => undefined,
|
|
151
|
+
getParsedHeader: () => ({seq: 1, method: 'INVITE'})
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
statusCode: 401,
|
|
155
|
+
source_address: '1.1.1.1',
|
|
156
|
+
source_port: 5060,
|
|
157
|
+
socket: {},
|
|
158
|
+
req,
|
|
159
|
+
agent: {request: () => {}},
|
|
160
|
+
has: (h) => h.toLowerCase() === 'www-authenticate',
|
|
161
|
+
get: (h) => h.toLowerCase() === 'www-authenticate' ? value : undefined
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
it('returns Error (does not throw) when www-authenticate value is undefined', function(done) {
|
|
166
|
+
new DigestClient(resWithChallenge(undefined)).authenticate((err) => {
|
|
167
|
+
err.should.be.an.Error();
|
|
168
|
+
err.message.should.match(/empty or non-string/);
|
|
169
|
+
done();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('returns Error when www-authenticate value is empty string', function(done) {
|
|
174
|
+
new DigestClient(resWithChallenge('')).authenticate((err) => {
|
|
175
|
+
err.should.be.an.Error();
|
|
176
|
+
err.message.should.match(/empty or non-string/);
|
|
177
|
+
done();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('_parseChallenge returns {} for undefined input', function() {
|
|
182
|
+
const out = new DigestClient(resWithChallenge('Digest realm="x",nonce="y"'))
|
|
183
|
+
._parseChallenge(undefined);
|
|
184
|
+
out.should.eql({});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('_parseChallenge returns {} for non-Digest input', function() {
|
|
188
|
+
const out = new DigestClient(resWithChallenge('Digest realm="x",nonce="y"'))
|
|
189
|
+
._parseChallenge('Basic realm="x"');
|
|
190
|
+
out.should.eql({});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('_parseChallenge captures digit-containing directive names (md5 algorithm)', function() {
|
|
194
|
+
const out = new DigestClient(resWithChallenge('Digest realm="x",nonce="y"'))
|
|
195
|
+
._parseChallenge('Digest realm="x",nonce="y",algorithm=MD5');
|
|
196
|
+
out.algorithm.should.eql('MD5');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
});
|