2023-10-03 11:14:36 +08:00
|
|
|
var semver = require('semver');
|
|
|
|
|
|
|
|
var i;
|
|
|
|
var keys;
|
|
|
|
var len;
|
|
|
|
|
|
|
|
var MESSAGE = exports.MESSAGE = {
|
|
|
|
// Transport layer protocol -- generic (1-19)
|
|
|
|
DISCONNECT: 1,
|
|
|
|
IGNORE: 2,
|
|
|
|
UNIMPLEMENTED: 3,
|
|
|
|
DEBUG: 4,
|
|
|
|
SERVICE_REQUEST: 5,
|
|
|
|
SERVICE_ACCEPT: 6,
|
|
|
|
|
|
|
|
// Transport layer protocol -- algorithm negotiation (20-29)
|
|
|
|
KEXINIT: 20,
|
|
|
|
NEWKEYS: 21,
|
|
|
|
|
|
|
|
// Transport layer protocol -- key exchange method-specific (30-49)
|
|
|
|
|
|
|
|
// User auth protocol -- generic (50-59)
|
|
|
|
USERAUTH_REQUEST: 50,
|
|
|
|
USERAUTH_FAILURE: 51,
|
|
|
|
USERAUTH_SUCCESS: 52,
|
|
|
|
USERAUTH_BANNER: 53,
|
|
|
|
|
|
|
|
// User auth protocol -- user auth method-specific (60-79)
|
|
|
|
|
|
|
|
// Connection protocol -- generic (80-89)
|
|
|
|
GLOBAL_REQUEST: 80,
|
|
|
|
REQUEST_SUCCESS: 81,
|
|
|
|
REQUEST_FAILURE: 82,
|
|
|
|
|
|
|
|
// Connection protocol -- channel-related (90-127)
|
|
|
|
CHANNEL_OPEN: 90,
|
|
|
|
CHANNEL_OPEN_CONFIRMATION: 91,
|
|
|
|
CHANNEL_OPEN_FAILURE: 92,
|
|
|
|
CHANNEL_WINDOW_ADJUST: 93,
|
|
|
|
CHANNEL_DATA: 94,
|
|
|
|
CHANNEL_EXTENDED_DATA: 95,
|
|
|
|
CHANNEL_EOF: 96,
|
|
|
|
CHANNEL_CLOSE: 97,
|
|
|
|
CHANNEL_REQUEST: 98,
|
|
|
|
CHANNEL_SUCCESS: 99,
|
|
|
|
CHANNEL_FAILURE: 100
|
|
|
|
|
|
|
|
// Reserved for client protocols (128-191)
|
|
|
|
|
|
|
|
// Local extensions (192-155)
|
|
|
|
};
|
|
|
|
for (i = 0, keys = Object.keys(MESSAGE), len = keys.length; i < len; ++i)
|
|
|
|
MESSAGE[MESSAGE[keys[i]]] = keys[i];
|
|
|
|
// context-specific message codes:
|
|
|
|
MESSAGE.KEXDH_INIT = 30;
|
|
|
|
MESSAGE.KEXDH_REPLY = 31;
|
|
|
|
MESSAGE.KEXDH_GEX_REQUEST = 34;
|
|
|
|
MESSAGE.KEXDH_GEX_GROUP = 31;
|
|
|
|
MESSAGE.KEXDH_GEX_INIT = 32;
|
|
|
|
MESSAGE.KEXDH_GEX_REPLY = 33;
|
|
|
|
MESSAGE.KEXECDH_INIT = 30; // included here for completeness
|
|
|
|
MESSAGE.KEXECDH_REPLY = 31; // included here for completeness
|
|
|
|
MESSAGE.USERAUTH_PASSWD_CHANGEREQ = 60;
|
|
|
|
MESSAGE.USERAUTH_PK_OK = 60;
|
|
|
|
MESSAGE.USERAUTH_INFO_REQUEST = 60;
|
|
|
|
MESSAGE.USERAUTH_INFO_RESPONSE = 61;
|
|
|
|
|
|
|
|
var DYNAMIC_KEXDH_MESSAGE = exports.DYNAMIC_KEXDH_MESSAGE = {};
|
|
|
|
DYNAMIC_KEXDH_MESSAGE[MESSAGE.KEXDH_GEX_GROUP] = 'KEXDH_GEX_GROUP';
|
|
|
|
DYNAMIC_KEXDH_MESSAGE[MESSAGE.KEXDH_GEX_REPLY] = 'KEXDH_GEX_REPLY';
|
|
|
|
|
|
|
|
var KEXDH_MESSAGE = exports.KEXDH_MESSAGE = {};
|
|
|
|
KEXDH_MESSAGE[MESSAGE.KEXDH_INIT] = 'KEXDH_INIT';
|
|
|
|
KEXDH_MESSAGE[MESSAGE.KEXDH_REPLY] = 'KEXDH_REPLY';
|
|
|
|
|
|
|
|
var DISCONNECT_REASON = exports.DISCONNECT_REASON = {
|
|
|
|
HOST_NOT_ALLOWED_TO_CONNECT: 1,
|
|
|
|
PROTOCOL_ERROR: 2,
|
|
|
|
KEY_EXCHANGE_FAILED: 3,
|
|
|
|
RESERVED: 4,
|
|
|
|
MAC_ERROR: 5,
|
|
|
|
COMPRESSION_ERROR: 6,
|
|
|
|
SERVICE_NOT_AVAILABLE: 7,
|
|
|
|
PROTOCOL_VERSION_NOT_SUPPORTED: 8,
|
|
|
|
HOST_KEY_NOT_VERIFIABLE: 9,
|
|
|
|
CONNECTION_LOST: 10,
|
|
|
|
BY_APPLICATION: 11,
|
|
|
|
TOO_MANY_CONNECTIONS: 12,
|
|
|
|
AUTH_CANCELED_BY_USER: 13,
|
|
|
|
NO_MORE_AUTH_METHODS_AVAILABLE: 14,
|
|
|
|
ILLEGAL_USER_NAME: 15
|
|
|
|
};
|
|
|
|
for (i = 0, keys = Object.keys(DISCONNECT_REASON), len = keys.length;
|
|
|
|
i < len;
|
|
|
|
++i) {
|
|
|
|
DISCONNECT_REASON[DISCONNECT_REASON[keys[i]]] = keys[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
var CHANNEL_OPEN_FAILURE = exports.CHANNEL_OPEN_FAILURE = {
|
|
|
|
ADMINISTRATIVELY_PROHIBITED: 1,
|
|
|
|
CONNECT_FAILED: 2,
|
|
|
|
UNKNOWN_CHANNEL_TYPE: 3,
|
|
|
|
RESOURCE_SHORTAGE: 4
|
|
|
|
};
|
|
|
|
for (i = 0, keys = Object.keys(CHANNEL_OPEN_FAILURE), len = keys.length;
|
|
|
|
i < len;
|
|
|
|
++i) {
|
|
|
|
CHANNEL_OPEN_FAILURE[CHANNEL_OPEN_FAILURE[keys[i]]] = keys[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
var TERMINAL_MODE = exports.TERMINAL_MODE = {
|
|
|
|
TTY_OP_END: 0, // Indicates end of options.
|
|
|
|
VINTR: 1, // Interrupt character; 255 if none. Similarly for the
|
|
|
|
// other characters. Not all of these characters are
|
|
|
|
// supported on all systems.
|
|
|
|
VQUIT: 2, // The quit character (sends SIGQUIT signal on POSIX
|
|
|
|
// systems).
|
|
|
|
VERASE: 3, // Erase the character to left of the cursor.
|
|
|
|
VKILL: 4, // Kill the current input line.
|
|
|
|
VEOF: 5, // End-of-file character (sends EOF from the terminal).
|
|
|
|
VEOL: 6, // End-of-line character in addition to carriage return
|
|
|
|
// and/or linefeed.
|
|
|
|
VEOL2: 7, // Additional end-of-line character.
|
|
|
|
VSTART: 8, // Continues paused output (normally control-Q).
|
|
|
|
VSTOP: 9, // Pauses output (normally control-S).
|
|
|
|
VSUSP: 10, // Suspends the current program.
|
|
|
|
VDSUSP: 11, // Another suspend character.
|
|
|
|
VREPRINT: 12, // Reprints the current input line.
|
|
|
|
VWERASE: 13, // Erases a word left of cursor.
|
|
|
|
VLNEXT: 14, // Enter the next character typed literally, even if it
|
|
|
|
// is a special character
|
|
|
|
VFLUSH: 15, // Character to flush output.
|
|
|
|
VSWTCH: 16, // Switch to a different shell layer.
|
|
|
|
VSTATUS: 17, // Prints system status line (load, command, pid, etc).
|
|
|
|
VDISCARD: 18, // Toggles the flushing of terminal output.
|
|
|
|
IGNPAR: 30, // The ignore parity flag. The parameter SHOULD be 0
|
|
|
|
// if this flag is FALSE, and 1 if it is TRUE.
|
|
|
|
PARMRK: 31, // Mark parity and framing errors.
|
|
|
|
INPCK: 32, // Enable checking of parity errors.
|
|
|
|
ISTRIP: 33, // Strip 8th bit off characters.
|
|
|
|
INLCR: 34, // Map NL into CR on input.
|
|
|
|
IGNCR: 35, // Ignore CR on input.
|
|
|
|
ICRNL: 36, // Map CR to NL on input.
|
|
|
|
IUCLC: 37, // Translate uppercase characters to lowercase.
|
|
|
|
IXON: 38, // Enable output flow control.
|
|
|
|
IXANY: 39, // Any char will restart after stop.
|
|
|
|
IXOFF: 40, // Enable input flow control.
|
|
|
|
IMAXBEL: 41, // Ring bell on input queue full.
|
|
|
|
ISIG: 50, // Enable signals INTR, QUIT, [D]SUSP.
|
|
|
|
ICANON: 51, // Canonicalize input lines.
|
|
|
|
XCASE: 52, // Enable input and output of uppercase characters by
|
|
|
|
// preceding their lowercase equivalents with "\".
|
|
|
|
ECHO: 53, // Enable echoing.
|
|
|
|
ECHOE: 54, // Visually erase chars.
|
|
|
|
ECHOK: 55, // Kill character discards current line.
|
|
|
|
ECHONL: 56, // Echo NL even if ECHO is off.
|
|
|
|
NOFLSH: 57, // Don't flush after interrupt.
|
|
|
|
TOSTOP: 58, // Stop background jobs from output.
|
|
|
|
IEXTEN: 59, // Enable extensions.
|
|
|
|
ECHOCTL: 60, // Echo control characters as ^(Char).
|
|
|
|
ECHOKE: 61, // Visual erase for line kill.
|
|
|
|
PENDIN: 62, // Retype pending input.
|
|
|
|
OPOST: 70, // Enable output processing.
|
|
|
|
OLCUC: 71, // Convert lowercase to uppercase.
|
|
|
|
ONLCR: 72, // Map NL to CR-NL.
|
|
|
|
OCRNL: 73, // Translate carriage return to newline (output).
|
|
|
|
ONOCR: 74, // Translate newline to carriage return-newline
|
|
|
|
// (output).
|
|
|
|
ONLRET: 75, // Newline performs a carriage return (output).
|
|
|
|
CS7: 90, // 7 bit mode.
|
|
|
|
CS8: 91, // 8 bit mode.
|
|
|
|
PARENB: 92, // Parity enable.
|
|
|
|
PARODD: 93, // Odd parity, else even.
|
|
|
|
TTY_OP_ISPEED: 128, // Specifies the input baud rate in bits per second.
|
|
|
|
TTY_OP_OSPEED: 129 // Specifies the output baud rate in bits per second.
|
|
|
|
};
|
|
|
|
for (i = 0, keys = Object.keys(TERMINAL_MODE), len = keys.length; i < len; ++i)
|
|
|
|
TERMINAL_MODE[TERMINAL_MODE[keys[i]]] = keys[i];
|
|
|
|
|
|
|
|
var CHANNEL_EXTENDED_DATATYPE = exports.CHANNEL_EXTENDED_DATATYPE = {
|
|
|
|
STDERR: 1
|
|
|
|
};
|
|
|
|
for (i = 0, keys = Object.keys(CHANNEL_EXTENDED_DATATYPE), len = keys.length;
|
|
|
|
i < len;
|
|
|
|
++i) {
|
|
|
|
CHANNEL_EXTENDED_DATATYPE[CHANNEL_EXTENDED_DATATYPE[keys[i]]] = keys[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.SIGNALS = ['ABRT', 'ALRM', 'FPE', 'HUP', 'ILL', 'INT',
|
|
|
|
'QUIT', 'SEGV', 'TERM', 'USR1', 'USR2', 'KILL',
|
|
|
|
'PIPE'];
|
|
|
|
|
|
|
|
var DEFAULT_KEX = [
|
|
|
|
'diffie-hellman-group14-sha1' // REQUIRED
|
|
|
|
];
|
|
|
|
var SUPPORTED_KEX = [
|
|
|
|
'diffie-hellman-group1-sha1' // REQUIRED
|
|
|
|
];
|
|
|
|
if (semver.gte(process.version, '0.11.12')) {
|
|
|
|
// https://tools.ietf.org/html/rfc4419#section-4
|
|
|
|
DEFAULT_KEX = [
|
|
|
|
'diffie-hellman-group-exchange-sha256'
|
|
|
|
].concat(DEFAULT_KEX);
|
|
|
|
SUPPORTED_KEX = [
|
|
|
|
'diffie-hellman-group-exchange-sha1'
|
|
|
|
].concat(SUPPORTED_KEX);
|
|
|
|
}
|
|
|
|
if (semver.gte(process.version, '0.11.14')) {
|
|
|
|
// https://tools.ietf.org/html/rfc5656#section-10.1
|
|
|
|
DEFAULT_KEX = [
|
|
|
|
'ecdh-sha2-nistp256',
|
|
|
|
'ecdh-sha2-nistp384',
|
|
|
|
'ecdh-sha2-nistp521'
|
|
|
|
].concat(DEFAULT_KEX);
|
|
|
|
}
|
|
|
|
var KEX_BUF = new Buffer(DEFAULT_KEX.join(','), 'ascii');
|
|
|
|
SUPPORTED_KEX = DEFAULT_KEX.concat(SUPPORTED_KEX);
|
|
|
|
|
|
|
|
var DEFAULT_SERVER_HOST_KEY = [
|
|
|
|
'ssh-rsa'
|
|
|
|
];
|
|
|
|
var SUPPORTED_SERVER_HOST_KEY = [
|
|
|
|
'ssh-dss'
|
|
|
|
];
|
|
|
|
if (semver.gte(process.version, '5.2.0')) {
|
|
|
|
// ECDSA keys are only supported in v5.2.0+ because of a crypto change that
|
|
|
|
// made it possible to (efficiently) generate an ECDSA public key from a
|
|
|
|
// private key (commit nodejs/node#da5ac55c83eb2c09cfb3baf7875529e8f1113529)
|
|
|
|
DEFAULT_SERVER_HOST_KEY.push(
|
|
|
|
'ecdsa-sha2-nistp256',
|
|
|
|
'ecdsa-sha2-nistp384',
|
|
|
|
'ecdsa-sha2-nistp521'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
var SERVER_HOST_KEY_BUF = new Buffer(DEFAULT_SERVER_HOST_KEY.join(','),
|
|
|
|
'ascii');
|
|
|
|
SUPPORTED_SERVER_HOST_KEY = DEFAULT_SERVER_HOST_KEY.concat(
|
|
|
|
SUPPORTED_SERVER_HOST_KEY
|
|
|
|
);
|
|
|
|
|
|
|
|
var DEFAULT_CIPHER = [];
|
|
|
|
var SUPPORTED_CIPHER = [
|
|
|
|
'aes256-cbc',
|
|
|
|
'aes192-cbc',
|
|
|
|
'aes128-cbc',
|
|
|
|
'blowfish-cbc',
|
|
|
|
'3des-cbc',
|
|
|
|
|
|
|
|
// http://tools.ietf.org/html/rfc4345#section-4:
|
|
|
|
'arcfour256',
|
|
|
|
'arcfour128',
|
|
|
|
|
|
|
|
'cast128-cbc',
|
|
|
|
'arcfour'
|
|
|
|
];
|
|
|
|
if (semver.gte(process.version, '0.11.12')) {
|
|
|
|
// node v0.11.12 introduced support for setting AAD, which is needed for
|
|
|
|
// AES-GCM in SSH2
|
|
|
|
DEFAULT_CIPHER = [
|
|
|
|
// http://tools.ietf.org/html/rfc5647
|
|
|
|
'aes128-gcm',
|
|
|
|
'aes128-gcm@openssh.com',
|
|
|
|
'aes256-gcm',
|
|
|
|
'aes256-gcm@openssh.com'
|
|
|
|
].concat(DEFAULT_CIPHER);
|
|
|
|
}
|
|
|
|
DEFAULT_CIPHER = [
|
|
|
|
// http://tools.ietf.org/html/rfc4344#section-4
|
|
|
|
'aes128-ctr',
|
|
|
|
'aes192-ctr',
|
|
|
|
'aes256-ctr'
|
|
|
|
].concat(DEFAULT_CIPHER);
|
|
|
|
var CIPHER_BUF = new Buffer(DEFAULT_CIPHER.join(','), 'ascii');
|
|
|
|
SUPPORTED_CIPHER = DEFAULT_CIPHER.concat(SUPPORTED_CIPHER);
|
|
|
|
|
|
|
|
var DEFAULT_HMAC = [
|
|
|
|
'hmac-sha2-256',
|
|
|
|
'hmac-sha2-512',
|
|
|
|
'hmac-sha1',
|
|
|
|
];
|
|
|
|
var SUPPORTED_HMAC = [
|
|
|
|
'hmac-md5',
|
|
|
|
'hmac-sha2-256-96', // first 96 bits of HMAC-SHA256
|
|
|
|
'hmac-sha2-512-96', // first 96 bits of HMAC-SHA512
|
|
|
|
'hmac-ripemd160',
|
|
|
|
'hmac-sha1-96', // first 96 bits of HMAC-SHA1
|
|
|
|
'hmac-md5-96' // first 96 bits of HMAC-MD5
|
|
|
|
];
|
|
|
|
var HMAC_BUF = new Buffer(DEFAULT_HMAC.join(','), 'ascii');
|
|
|
|
SUPPORTED_HMAC = DEFAULT_HMAC.concat(SUPPORTED_HMAC);
|
|
|
|
|
|
|
|
var DEFAULT_COMPRESS = [
|
|
|
|
'none',
|
|
|
|
'zlib@openssh.com', // ZLIB (LZ77) compression, except
|
|
|
|
// compression/decompression does not start until after
|
|
|
|
// successful user authentication
|
|
|
|
'zlib' // ZLIB (LZ77) compression
|
|
|
|
];
|
|
|
|
var SUPPORTED_COMPRESS = [];
|
|
|
|
var COMPRESS_BUF = new Buffer(DEFAULT_COMPRESS.join(','), 'ascii');
|
|
|
|
SUPPORTED_COMPRESS = DEFAULT_COMPRESS.concat(SUPPORTED_COMPRESS);
|
|
|
|
|
|
|
|
exports.ALGORITHMS = {
|
|
|
|
KEX: DEFAULT_KEX,
|
|
|
|
KEX_BUF: KEX_BUF,
|
|
|
|
SUPPORTED_KEX: SUPPORTED_KEX,
|
|
|
|
|
|
|
|
SERVER_HOST_KEY: DEFAULT_SERVER_HOST_KEY,
|
|
|
|
SERVER_HOST_KEY_BUF: SERVER_HOST_KEY_BUF,
|
|
|
|
SUPPORTED_SERVER_HOST_KEY: SUPPORTED_SERVER_HOST_KEY,
|
|
|
|
|
|
|
|
CIPHER: DEFAULT_CIPHER,
|
|
|
|
CIPHER_BUF: CIPHER_BUF,
|
|
|
|
SUPPORTED_CIPHER: SUPPORTED_CIPHER,
|
|
|
|
|
|
|
|
HMAC: DEFAULT_HMAC,
|
|
|
|
HMAC_BUF: HMAC_BUF,
|
|
|
|
SUPPORTED_HMAC: SUPPORTED_HMAC,
|
|
|
|
|
|
|
|
COMPRESS: DEFAULT_COMPRESS,
|
|
|
|
COMPRESS_BUF: COMPRESS_BUF,
|
|
|
|
SUPPORTED_COMPRESS: SUPPORTED_COMPRESS
|
|
|
|
};
|
|
|
|
exports.SSH_TO_OPENSSL = {
|
|
|
|
// ECDH key exchange
|
|
|
|
'ecdh-sha2-nistp256': 'prime256v1', // OpenSSL's name for 'secp256r1'
|
|
|
|
'ecdh-sha2-nistp384': 'secp384r1',
|
|
|
|
'ecdh-sha2-nistp521': 'secp521r1',
|
|
|
|
// Ciphers
|
|
|
|
'aes128-gcm': 'aes-128-gcm',
|
|
|
|
'aes256-gcm': 'aes-256-gcm',
|
|
|
|
'aes128-gcm@openssh.com': 'aes-128-gcm',
|
|
|
|
'aes256-gcm@openssh.com': 'aes-256-gcm',
|
|
|
|
'3des-cbc': 'des-ede3-cbc',
|
|
|
|
'blowfish-cbc': 'bf-cbc',
|
|
|
|
'aes256-cbc': 'aes-256-cbc',
|
|
|
|
'aes192-cbc': 'aes-192-cbc',
|
|
|
|
'aes128-cbc': 'aes-128-cbc',
|
|
|
|
'idea-cbc': 'idea-cbc',
|
|
|
|
'cast128-cbc': 'cast-cbc',
|
|
|
|
'rijndael-cbc@lysator.liu.se': 'aes-256-cbc',
|
|
|
|
'arcfour128': 'rc4',
|
|
|
|
'arcfour256': 'rc4',
|
|
|
|
'arcfour512': 'rc4',
|
|
|
|
'arcfour': 'rc4',
|
|
|
|
'camellia128-cbc': 'camellia-128-cbc',
|
|
|
|
'camellia192-cbc': 'camellia-192-cbc',
|
|
|
|
'camellia256-cbc': 'camellia-256-cbc',
|
|
|
|
'camellia128-cbc@openssh.com': 'camellia-128-cbc',
|
|
|
|
'camellia192-cbc@openssh.com': 'camellia-192-cbc',
|
|
|
|
'camellia256-cbc@openssh.com': 'camellia-256-cbc',
|
|
|
|
'3des-ctr': 'des-ede3',
|
|
|
|
'blowfish-ctr': 'bf-ecb',
|
|
|
|
'aes256-ctr': 'aes-256-ctr',
|
|
|
|
'aes192-ctr': 'aes-192-ctr',
|
|
|
|
'aes128-ctr': 'aes-128-ctr',
|
|
|
|
'cast128-ctr': 'cast5-ecb',
|
|
|
|
'camellia128-ctr': 'camellia-128-ecb',
|
|
|
|
'camellia192-ctr': 'camellia-192-ecb',
|
|
|
|
'camellia256-ctr': 'camellia-256-ecb',
|
|
|
|
'camellia128-ctr@openssh.com': 'camellia-128-ecb',
|
|
|
|
'camellia192-ctr@openssh.com': 'camellia-192-ecb',
|
|
|
|
'camellia256-ctr@openssh.com': 'camellia-256-ecb',
|
|
|
|
// HMAC
|
|
|
|
'hmac-sha1-96': 'sha1',
|
|
|
|
'hmac-sha1': 'sha1',
|
|
|
|
'hmac-sha2-256': 'sha256',
|
|
|
|
'hmac-sha2-256-96': 'sha256',
|
|
|
|
'hmac-sha2-512': 'sha512',
|
|
|
|
'hmac-sha2-512-96': 'sha512',
|
|
|
|
'hmac-md5-96': 'md5',
|
|
|
|
'hmac-md5': 'md5',
|
|
|
|
'hmac-ripemd160': 'ripemd160'
|
|
|
|
};
|
|
|
|
|
|
|
|
var BUGS = exports.BUGS = {
|
|
|
|
BAD_DHGEX: 1,
|
|
|
|
OLD_EXIT: 2,
|
|
|
|
DYN_RPORT_BUG: 4
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.BUGGY_IMPLS = [
|
|
|
|
[ 'Cisco-1.25', BUGS.BAD_DHGEX ],
|
|
|
|
[ /^[0-9.]+$/, BUGS.OLD_EXIT ], // old SSH.com implementations
|
|
|
|
[ /^OpenSSH_5\.\d+/, BUGS.DYN_RPORT_BUG ]
|
|
|
|
];
|