Comparing version 0.1.3 to 0.1.4
@@ -24,3 +24,3 @@ var Socket = require('net').Socket; | ||
sock.on('connect', function() { | ||
sock.once('connect', function() { | ||
var buf; | ||
@@ -155,6 +155,6 @@ if (isSigning) { | ||
}); | ||
sock.on('error', function(err) { | ||
sock.once('error', function(err) { | ||
error = err; | ||
}); | ||
sock.on('close', function(had_err) { | ||
sock.once('close', function(had_err) { | ||
if (error) | ||
@@ -161,0 +161,0 @@ cb(error); |
@@ -7,3 +7,4 @@ var inherits = require('util').inherits, | ||
var MAX_WINDOW = Math.pow(2, 32) - 1, | ||
var MAX_WINDOW = 0x100000, // 1MB (protocol supports up to Math.pow(2, 32) - 1) | ||
WINDOW_THRESH = MAX_WINDOW / 2, | ||
SIGNALS = ['ABRT', 'ALRM', 'FPE', 'HUP', 'ILL', 'INT', 'KILL', 'PIPE', | ||
@@ -38,3 +39,3 @@ 'QUIT', 'SEGV', 'TERM', 'USR1', 'USR2'], | ||
conn._parser.on('CHANNEL_EOF:' + this.incoming.id, function() { | ||
conn._parser.once('CHANNEL_EOF:' + this.incoming.id, function() { | ||
self.incoming.state = 'eof'; | ||
@@ -48,3 +49,3 @@ if (self._stream) { | ||
conn._parser.on('CHANNEL_CLOSE:' + this.incoming.id, function() { | ||
conn._parser.once('CHANNEL_CLOSE:' + this.incoming.id, function() { | ||
self.incoming.state = 'closed'; | ||
@@ -64,2 +65,5 @@ if (self.outgoing.state === 'open' || self.outgoing.state === 'eof') | ||
conn._parser.on('CHANNEL_DATA:' + this.incoming.id, function(data) { | ||
self.incoming.window -= data.length; | ||
if (self.incoming.window <= WINDOW_THRESH) | ||
self._sendWndAdjust(); | ||
if (self._stream) { | ||
@@ -74,2 +78,5 @@ if (self._stream._decoder) | ||
function(type, data) { | ||
self.incoming.window -= data.length; | ||
if (self.incoming.window <= WINDOW_THRESH) | ||
self._sendWndAdjust(); | ||
if (self._stream) { | ||
@@ -84,2 +91,11 @@ if (self._stream._decoder) | ||
conn._parser.on('CHANNEL_WINDOW_ADJUST:' + this.incoming.id, function(amt) { | ||
// the server is allowing us to send `amt` more bytes of data | ||
self.outgoing.window += amt; | ||
if (self._stream && self._stream.outpaused) { | ||
self._stream.outpaused = false; | ||
self._stream._drainOutBuffer(); | ||
} | ||
}); | ||
conn._parser.on('CHANNEL_SUCCESS:' + this.incoming.id, function() { | ||
@@ -348,2 +364,3 @@ if (self._callbacks.length) | ||
Channel.prototype._sendSubsystem = function(name, cb) { | ||
// Note: CHANNEL_REQUEST does not consume window space | ||
/* | ||
@@ -381,6 +398,8 @@ byte SSH_MSG_CHANNEL_REQUEST | ||
while (len - p > 0) { | ||
if (this.outgoing.window === 0) | ||
this._sendWndAdjust(); | ||
sliceLen = (len - p < this.outgoing.window ? len - p : this.outgoing.window); | ||
while (len - p > 0 && this.outgoing.window > 0) { | ||
sliceLen = len - p; | ||
if (sliceLen > this.outgoing.window) | ||
sliceLen = this.outgoing.window; | ||
if (sliceLen > this.outgoing.packetSize) | ||
sliceLen = this.outgoing.packetSize; | ||
if (extendedType === undefined) { | ||
@@ -417,2 +436,18 @@ /* | ||
// Will we ever be in a "good" state and be sending data without a | ||
// ChannelStream? | ||
if (len - p > 0 && this._stream) { | ||
// buffer outbound data until server sends us a CHANNEL_WINDOW_ADJUST message | ||
if (p > 0) { | ||
// partial | ||
buf = new Buffer(len - p); | ||
data.copy(buf, 0, p); | ||
this._stream._outbuffer.push([buf, extendedType]); | ||
} else | ||
this._stream._outbuffer.push([data, extendedType]); | ||
if (ret) | ||
ret = false; | ||
this._stream.outpaused = true; | ||
} | ||
return ret; | ||
@@ -427,3 +462,3 @@ }; | ||
*/ | ||
amt = amt || Math.min(MAX_WINDOW, this.outgoing.packetSize); | ||
amt = amt || MAX_WINDOW; | ||
var buf = new Buffer(1 + 4 + 4); | ||
@@ -434,3 +469,3 @@ buf[0] = MESSAGE.CHANNEL_WINDOW_ADJUST; | ||
this.outgoing.window += amt; | ||
this.incoming.window += amt; | ||
@@ -448,3 +483,2 @@ return this._conn._send(buf); | ||
function ChannelStream(channel) { | ||
// TODO: update readable and writable appropriately | ||
var self = this; | ||
@@ -454,11 +488,13 @@ this.readable = true; | ||
this.paused = false; | ||
this.outpaused = false; | ||
this.allowHalfOpen = false; | ||
this._channel = channel; | ||
this._buffer = []; | ||
this._outbuffer = []; | ||
this._inbuffer = []; | ||
this._decoder = undefined; | ||
channel._conn._sock.on('end', function() { | ||
channel._conn._sock.once('end', function() { | ||
self.writable = false; | ||
self.readable = false; | ||
}); | ||
channel._conn._sock.on('close', function() { | ||
channel._conn._sock.once('close', function() { | ||
self.writable = false; | ||
@@ -470,3 +506,96 @@ self.readable = false; | ||
ChannelStream.prototype._emit = ChannelStream.prototype.emit; | ||
ChannelStream.prototype.emit = function(ev, arg1, arg2, arg3, arg4, arg5) { | ||
if (this.paused) { | ||
if (arg1 === undefined) | ||
this._inbuffer.push([ev]); | ||
else if (arg2 === undefined) | ||
this._inbuffer.push([ev, arg1]); | ||
else if (arg3 === undefined) | ||
this._inbuffer.push([ev, arg1, arg2]); | ||
else if (arg4 === undefined) | ||
this._inbuffer.push([ev, arg1, arg2, arg3]); | ||
else if (arg5 === undefined) | ||
this._inbuffer.push([ev, arg1, arg2, arg3, arg4]); | ||
else | ||
this._inbuffer.push([ev, arg1, arg2, arg3, arg4, arg5]); | ||
} else { | ||
if (ev === 'data' && this._decoder) | ||
this._emit(ev, this._decoder.write(arg1), arg2); | ||
else if (arg1 === undefined) | ||
this._emit(ev); | ||
else if (arg2 === undefined) | ||
this._emit(ev, arg1); | ||
else if (arg3 === undefined) | ||
this._emit(ev, arg1, arg2); | ||
else if (arg4 === undefined) | ||
this._emit(ev, arg1, arg2, arg3); | ||
else if (arg5 === undefined) | ||
this._emit(ev, arg1, arg2, arg3, arg4); | ||
else | ||
this._emit(ev, arg1, arg2, arg3, arg4, arg5); | ||
} | ||
}; | ||
ChannelStream.prototype._drainInBuffer = function() { | ||
var i = 0, val, vallen, len = this._inbuffer.length, ret; | ||
for (; i < len; ++i) { | ||
val = this._inbuffer[i]; | ||
vallen = val.length; | ||
if (val[0] === 'data' && this._decoder) | ||
this._emit(val[0], this._decoder.write(val[1]), val[2]); | ||
else if (vallen === 1) | ||
this._emit(val[0]); | ||
else if (vallen === 2) | ||
this._emit(val[0], val[1]); | ||
else if (vallen === 3) | ||
this._emit(val[0], val[1], val[2]); | ||
else if (vallen === 4) | ||
this._emit(val[0], val[1], val[2], val[3]); | ||
else if (vallen === 5) | ||
this._emit(val[0], val[1], val[2], val[3], val[4]); | ||
else | ||
this._emit(val[0], val[1], val[2], val[3], val[4], val[5]); | ||
} | ||
}; | ||
ChannelStream.prototype._drainOutBuffer = function() { | ||
if (!this.writable) | ||
return; | ||
var i = 0, len = this._outbuffer.length, ret = true; | ||
for (; i < len; ++i) { | ||
if (this._outbuffer[i] === null) { | ||
// end() was called | ||
ret = true; | ||
len = 0; // bypass length check | ||
this.destroy(); | ||
break; | ||
} else if (this.outpaused) | ||
break; | ||
else | ||
ret = this._channel._sendData(this._outbuffer[i][0], this._outbuffer[i][1]); | ||
} | ||
// it's possible _sendData pushed more data into the outbuffer if we ran out | ||
// of window space while in the above for-loop. we check for that here ... | ||
if (len === this._outbuffer.length) { | ||
if (len) | ||
this._outbuffer = []; | ||
} else { | ||
this._outbuffer.splice(0, i); | ||
ret = false; | ||
} | ||
if (ret) | ||
this.emit('drain'); | ||
return ret; | ||
}; | ||
ChannelStream.prototype.write = function(data, encoding, extended) { | ||
if (!this.writable) | ||
throw new Error('ChannelStream is not writable'); | ||
var extendedType; | ||
@@ -490,4 +619,4 @@ | ||
if (this.paused) { | ||
this._buffer.push([data, extended]); | ||
if (this.outpaused) { | ||
this._outbuffer.push([data, extended]); | ||
return false; | ||
@@ -511,21 +640,3 @@ } else { | ||
this.paused = false; | ||
var i = 0, len = this._buffer.length, ret; | ||
for (; i < len; ++i) { | ||
if (this._buffer[i] === null) { | ||
ret = this._channel.eof(); | ||
ret = this._channel.close(); | ||
this.writable = false; | ||
this.readable = false; | ||
break; | ||
} else | ||
ret = this._channel._sendData(this._buffer[i][0], this._buffer[i][1]); | ||
} | ||
if (len) | ||
this._buffer = []; | ||
if (ret === true) | ||
this.emit('drain'); | ||
this._drainInBuffer(); | ||
this._channel._conn._sock.resume(); | ||
@@ -535,7 +646,9 @@ }; | ||
ChannelStream.prototype.end = function(data, encoding, extended) { | ||
if (!this.writable) | ||
return; | ||
var ret; | ||
if (data && data.length) | ||
ret = this.write(data, encoding, extended); | ||
if (this.paused) { | ||
ret = this._buffer.push(null); | ||
if (this.outpaused) { | ||
ret = this._outbuffer.push(null); | ||
this.resume(); | ||
@@ -555,7 +668,14 @@ } else { | ||
ChannelStream.prototype.destroy = function() { | ||
this._channel.eof(); | ||
this._channel.close(); | ||
this._buffer = []; | ||
var ret; | ||
ret = this._channel.eof(); | ||
ret = this._channel.close(); | ||
if (this._outbuffer.length) | ||
this._outbuffer = []; | ||
if (this._inbuffer.length) | ||
this._inbuffer = []; | ||
this.writable = false; | ||
this.readable = false; | ||
this.paused = false; | ||
this.outpaused = false; | ||
return ret; | ||
}; | ||
@@ -562,0 +682,0 @@ |
@@ -24,3 +24,4 @@ var net = require('net'), | ||
DISCONNECT_REASON = consts.DISCONNECT_REASON, | ||
CHANNEL_OPEN_FAILURE = consts.CHANNEL_OPEN_FAILURE; | ||
CHANNEL_OPEN_FAILURE = consts.CHANNEL_OPEN_FAILURE, | ||
PING_PACKET = new Buffer([MESSAGE.IGNORE, 0, 0, 0, 0]); | ||
@@ -307,2 +308,3 @@ function isStreamCipher(cipher) { | ||
} | ||
// verify the host fingerprint first if needed | ||
if (self._state === 'initexchg' && self._fingerprint && self._cbfingerprint) { | ||
@@ -327,3 +329,3 @@ var hostHash = require('crypto').createHash(self._fingerprint); | ||
info.secret = new Buffer(compSecret, 'binary'); | ||
// SHA1 for both currently supported DH kex methods | ||
// SHA1 for supported DH group1 and group14 kex methods | ||
var hash = crypto.createHash('sha1'); | ||
@@ -645,3 +647,3 @@ var len_ident = Buffer.byteLength(SSH_IDENT), | ||
if (self._state === 'initexchg') { | ||
// attempt to begin to perform user auth | ||
// begin to perform user auth | ||
var svcBuf = new Buffer(1 + 4 + 12); | ||
@@ -654,2 +656,4 @@ svcBuf[0] = MESSAGE.SERVICE_REQUEST; | ||
self._state = 'authenticated'; | ||
// empty our outbound buffer of any data we tried to send while the key | ||
// re-exchange was happening | ||
var b = 0, blen = self._buffer.length; | ||
@@ -670,2 +674,4 @@ for (; b < blen; ++b) { | ||
this._parser.on('SERVICE_ACCEPT', function(svc) { | ||
// we previously sent a request to start the process of user authentication | ||
// and the server is allowing us to continue | ||
if (svc === 'ssh-userauth') { | ||
@@ -682,2 +688,3 @@ if (self._password) | ||
this._parser.on('USERAUTH_SUCCESS', function() { | ||
// we successfully authenticated with the server | ||
self._state = 'authenticated'; | ||
@@ -691,2 +698,8 @@ if (self._parser._authMethod === 'password' | ||
self._agentKeys = undefined; | ||
if (typeof self._pingInterval === 'number') { | ||
self._pinger = setInterval(function() { | ||
self._ping(); | ||
}, self._pingInterval); | ||
} | ||
self.emit('ready'); | ||
@@ -696,2 +709,3 @@ }); | ||
this._parser.on('USERAUTH_FAILURE', function(auths, partial) { | ||
// we failed to authenticate with the server for whatever reason | ||
if (self._parser._authMethod === 'password' | ||
@@ -709,2 +723,4 @@ && self._parser._newpwd !== undefined) { | ||
this._parser.on('USERAUTH_BANNER', function(message, lang) { | ||
// the server sent us a notice/banner of some kind for the user to read | ||
// before attempting to log in, usually a legal notice or some such | ||
self.emit('banner', message, lang); | ||
@@ -714,2 +730,4 @@ }); | ||
this._parser.on('USERAUTH_PASSWD_CHANGEREQ', function(message, lang) { | ||
// we tried to authenticate via password, but the server says we need to | ||
// change our password first | ||
self._parser._newpwd = undefined; | ||
@@ -728,2 +746,4 @@ self.emit('change password', message, lang, function(newpwd) { | ||
this._parser.on('USERAUTH_INFO_REQUEST', function(name, inst, lang, prompts) { | ||
// we sent a keyboard-interactive user authentication request and now the | ||
// server is sending us the prompts we need to present to the user | ||
self.emit('keyboard-interactive', name, inst, lang, prompts, | ||
@@ -760,2 +780,5 @@ function(answers) { | ||
this._parser.on('USERAUTH_PK_OK', function() { | ||
// server says our public key is permitted for user authentication, so | ||
// continue on with real user authentication request | ||
// (signing data with private key) | ||
self._authPK(true); | ||
@@ -765,2 +788,4 @@ }); | ||
this._parser.on('REQUEST_SUCCESS', function(data) { | ||
// general success response -- one of two replies sent when a packet's | ||
// "want_reply" is set to true | ||
if (self._callbacks.length) | ||
@@ -771,2 +796,4 @@ self._callbacks.shift()(false, data); | ||
this._parser.on('REQUEST_FAILURE', function() { | ||
// general failure response -- one of two replies sent when a packet's | ||
// "want_reply" is set to true | ||
if (self._callbacks.length) | ||
@@ -777,2 +804,5 @@ self._callbacks.shift()(true); | ||
this._parser.on('CHANNEL_OPEN', function(info) { | ||
// the server is trying to open a channel with us, this is usually when | ||
// we asked the server to forward us connections on some port and now they | ||
// are asking us to accept/deny an incoming connection on their side | ||
if (info.type === 'forwarded-tcpip') { | ||
@@ -792,5 +822,2 @@ var rejectConn = false, localChan; | ||
if (rejectConn) | ||
reject(); | ||
// TODO: automatic rejection after some timeout? | ||
@@ -857,2 +884,5 @@ var accept = function() { | ||
if (rejectConn) | ||
reject(); | ||
if (localChan !== false) | ||
@@ -895,2 +925,3 @@ self.emit('tcp connection', info.data, accept, reject); | ||
this._agent = opts.agent; // process.env.SSH_AUTH_SOCK | ||
this._pingInterval = opts.pingInterval; | ||
@@ -913,2 +944,3 @@ this._sock = new net.Socket(); | ||
this._sessionid = undefined; | ||
this._pinger = undefined; | ||
@@ -997,2 +1029,3 @@ this._curChan = -1; | ||
if (!this._privateKey.public) { | ||
// we get here if we didn't load a PuTTY key file | ||
if (this._publicKey) { | ||
@@ -1007,2 +1040,3 @@ keyInfo = keyParser(this._publicKey); | ||
} else { | ||
// parsing private key in ASN.1 format | ||
var i = 2, len, octets, | ||
@@ -1168,3 +1202,3 @@ privKey = this._privateKey.private, | ||
this._sock.on('connect', function() { | ||
this._sock.once('connect', function() { | ||
self._state = 'initexchg'; | ||
@@ -1177,11 +1211,19 @@ self.emit('connect'); | ||
}); | ||
this._sock.on('error', function(err) { | ||
this._sock.once('error', function(err) { | ||
err.level = 'connection-socket'; | ||
self.emit('error', err); | ||
}); | ||
this._sock.on('end', function() { | ||
this._sock.once('end', function() { | ||
if (self._pinger) { | ||
clearInterval(self._pinger); | ||
self._pinger = undefined; | ||
} | ||
self._state = 'closed'; | ||
self.emit('end'); | ||
}); | ||
this._sock.on('close', function(had_err) { | ||
this._sock.once('close', function(had_err) { | ||
if (self._pinger) { | ||
clearInterval(self._pinger); | ||
self._pinger = undefined; | ||
} | ||
self._parser.reset(); | ||
@@ -1200,2 +1242,3 @@ self._state = 'closed'; | ||
Connection.prototype.exec = function(cmd, env, cb) { | ||
// execute an arbitrary command on the server | ||
if (typeof env === 'function') { | ||
@@ -1216,2 +1259,3 @@ cb = env; | ||
Connection.prototype.shell = function(window, cb) { | ||
// start an interactive terminal/shell session | ||
var rows = 24, cols = 80, width = 640, height = 480, term = 'vt100'; | ||
@@ -1245,2 +1289,4 @@ | ||
Connection.prototype.forwardIn = function(address, port, cb) { | ||
// send a request for the server to start forwarding TCP connections to us | ||
// on a particular address and port | ||
/* | ||
@@ -1284,2 +1330,4 @@ byte SSH_MSG_GLOBAL_REQUEST | ||
Connection.prototype.unforwardIn = function(address, port, cb) { | ||
// send a request to stop forwarding traffic from the server to us for a | ||
// particular address and port | ||
/* | ||
@@ -1314,2 +1362,3 @@ byte SSH_MSG_GLOBAL_REQUEST | ||
Connection.prototype.forwardOut = function(srcIP, srcPort, dstIP, dstPort, cb) { | ||
// send a request to forward a TCP connection to the server | ||
/* | ||
@@ -1348,2 +1397,3 @@ byte SSH_MSG_CHANNEL_OPEN | ||
Connection.prototype.sftp = function(cb) { | ||
// start an SFTP session | ||
return this._openChan('session', function(err, chan) { | ||
@@ -1365,3 +1415,3 @@ if (err) | ||
}); | ||
sftp.on('close', function() { | ||
sftp.once('close', function() { | ||
stream.end(); | ||
@@ -1378,4 +1428,8 @@ }); | ||
Connection.prototype._openChan = function(type, blob, cb) { | ||
// ask the server to open a channel for some purpose (e.g. session (sftp, exec, | ||
// terminal), or forwarding a TCP connection to the server) | ||
var self = this, | ||
localChan = this._nextChan(); | ||
localChan = this._nextChan(), | ||
inWindow = Channel.MAX_WINDOW, | ||
inPktSize = Channel.MAX_WINDOW; | ||
@@ -1398,4 +1452,4 @@ if (localChan === false) | ||
id: localChan, | ||
window: Channel.MAX_WINDOW, | ||
packetSize: Channel.MAX_WINDOW, | ||
window: inWindow, | ||
packetSize: inPktSize, | ||
state: 'open' | ||
@@ -1437,4 +1491,4 @@ }, | ||
buf.writeUInt32BE(localChan, p, true); | ||
buf.writeUInt32BE(Channel.MAX_WINDOW, p += 4, true); | ||
buf.writeUInt32BE(Channel.MAX_WINDOW, p += 4, true); | ||
buf.writeUInt32BE(inWindow, p += 4, true); | ||
buf.writeUInt32BE(inPktSize, p += 4, true); | ||
if (blob) | ||
@@ -1447,2 +1501,5 @@ blob.copy(buf, p += 4); | ||
Connection.prototype._nextChan = function() { | ||
// get the next available channel number | ||
// optimized path | ||
if (this._curChan < MAX_CHANNEL) | ||
@@ -1452,2 +1509,3 @@ if (++this._curChan <= MAX_CHANNEL) | ||
// slower lookup path | ||
for (var i = 0; i < MAX_CHANNEL; ++i) | ||
@@ -1460,3 +1518,11 @@ if (this._channels.indexOf(i)) | ||
Connection.prototype._ping = function() { | ||
// simply send an SSH_MSG_IGNORE message for pinging purposes | ||
this._send(PING_PACKET); | ||
}; | ||
Connection.prototype._tryNextAuth = function(noAgent) { | ||
// try the next user authentication mechanism: | ||
// for ssh-agent users, this means the next public key stored in ssh-agent. | ||
// otherwise possibly try keyboard-interactive before erroring out | ||
if (this._agent && !noAgent) | ||
@@ -1479,2 +1545,3 @@ this._authAgent(); | ||
Connection.prototype._authPwd = function(newpwd) { | ||
// attempt to authenticate via password | ||
/* | ||
@@ -1533,2 +1600,3 @@ "Normal" password auth: | ||
Connection.prototype._authKeyboard = function() { | ||
// attempt to authenticate via keyboard-interactive | ||
/* | ||
@@ -1567,2 +1635,3 @@ byte SSH_MSG_USERAUTH_REQUEST | ||
Connection.prototype._authPK = function(sign) { | ||
// attempt to authenticate via key | ||
this._parser._authMethod = 'pubkey'; | ||
@@ -1724,2 +1793,3 @@ /* | ||
Connection.prototype._authAgent = function() { | ||
// attempt to authenticate via ssh-agent | ||
this._parser._authMethod = 'agent'; | ||
@@ -1726,0 +1796,0 @@ |
@@ -540,2 +540,11 @@ // TODO: * Filter control codes from strings | ||
break; | ||
case MESSAGE.CHANNEL_WINDOW_ADJUST: | ||
/* | ||
byte SSH_MSG_CHANNEL_WINDOW_ADJUST | ||
uint32 recipient channel | ||
uint32 bytes to add | ||
*/ | ||
this.emit('CHANNEL_WINDOW_ADJUST:' + payload.readUInt32BE(1, true), | ||
payload.readUInt32BE(5, true)); | ||
break; | ||
case MESSAGE.CHANNEL_SUCCESS: | ||
@@ -542,0 +551,0 @@ /* |
{ "name": "ssh2", | ||
"version": "0.1.3", | ||
"version": "0.1.4", | ||
"author": "Brian White <mscdex@mscdex.net>", | ||
@@ -4,0 +4,0 @@ "description": "An SSH2 client module written in pure JavaScript for node.js", |
@@ -390,2 +390,4 @@ | ||
* **pingInterval** - < _integer_ > - How often to send SSH-level keepalive packets to the server. **Default:** (no keepalive) | ||
* **exec**(< _string_ >command[, < _object_ >environment], < _function_ >callback) - _(void)_ - Executes `command` on the server, with an optional `environment` set before execution. `callback` has 2 parameters: < _Error_ >err, < _ChannelStream_ >stream. For exec, the `stream` will also emit 'exit' when the process finishes. If the process finished normally, the process return value is passed to the 'exit' callback. If the process was interrupted by a signal, the following are passed to the 'exit' callback: null, < _string_ >signalName, < _boolean_ >didCoreDump, < _string_ >description. | ||
@@ -392,0 +394,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
183407
4568
499