Skip to content
Permalink
Fetching contributors…
Cannot retrieve contributors at this time
358 lines (312 sloc) 9.14 KB
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at http://smartos.org/CDDL
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file.
*
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2020 Joyent, Inc. All rights reserved.
*
*/
var assert = require('assert');
var fs = require('fs');
var ipaddr = require('/usr/vm/node_modules/ip');
var macaddr = require('/usr/vm/node_modules/macaddr');
var net = require('net');
var sprintf = require('/usr/node/node_modules/sprintf').sprintf;
function addString(buf, str, pos)
{
var len = str.length;
buf.write(str, pos);
return (len + 1);
}
/*
* When you need to access files inside a zoneroot, you need to be careful that
* there are no symlinks in the path. Since we operate from the GZ, these
* symlinks will be evaluated in the GZ context. Eg. a symlink in zone A with
* /var/run -> * /zones/<uuid of zone B>/root/var/run would mean that operating
* on files in zone A's /var/run would actually be touching files in zone B.
*
* To prevent that, only ever modify files inside the zoneroot from the GZ
* *before* first boot. After the zone is booted, it's better to use services
* in the zone to pull values from metadata and write out changes on next boot.
* It's also safe to use zlogin when the zone is running.
*
* This function is intended to be used in those cases we do write things out
* before the zone's first boot but the dataset might have invalid symlinks in
* it even then, so we still need to confirm the paths inside zoneroot before
* using them. It throws an exception if:
*
* - zoneroot is not an absolute path
* - fs.lstatSync fails
* - target path under zoneroot contains symlink
* - a component leading up to the final one is not a directory
* - options.type is set to 'file' and target is not a regular file
* - options.type is set to 'dir' and target references a non-directory
* - options.type is not one of 'file' or 'dir'
* - options.enoent_ok is false and target path doesn't exist
*
* if none of those are the case, it returns true.
*/
function assertSafeZonePath(zoneroot, target, options)
{
var parts;
var root;
var stat;
var test;
assert((zoneroot.length > 0 && zoneroot[0] === '/'),
'zoneroot must be an absolute path, not: [' + zoneroot + ']');
parts = trim(target, '/').split('/');
root = trim(zoneroot, '/');
test = '/' + root;
while (parts.length > 0) {
test = test + '/' + parts.shift();
try {
stat = fs.lstatSync(test);
} catch (e) {
if (e.code === 'ENOENT') {
if (!options.hasOwnProperty('enoent_ok')
|| options.enoent_ok === false) {
throw e;
} else {
// enoent is ok, return true. This is mostly used when
// deleting files with rm -f <path>. It's ok for <path> to
// not exist (but not ok for any component to be a symlink)
// there's no point continuing though since ENOENT here
// means all subpaths also won't exist.
return true;
}
} else {
throw e;
}
}
if (stat.isSymbolicLink()) {
// it's never ok to have a symlink component
throw new Error(test + ' is a symlink');
}
// any component other than the last also needs to be a
// directory, last can also be a file.
if (parts.length === 0) {
// last, dir or file
if (!options.hasOwnProperty('type') || options.type === 'dir') {
if (!stat.isDirectory()) {
throw new Error(test + ' is not a directory');
}
} else if (options.type === 'file') {
if (!stat.isFile()) {
throw new Error(test + ' is not a file');
}
} else {
throw new Error('this function does not know about '
+ options.type);
}
} else if (!stat.isDirectory()) {
// not last component, only dir is acceptable
throw new Error(test + ' is not a directory');
}
}
// if we didn't throw, this is valid.
return true;
}
function epochTimestampSecs(str)
{
var secs = Number(str);
var t = new Date(secs * 1000);
return t.toISOString();
}
function fixBoolean(str)
{
if (str === 'true') {
return true;
} else if (str === 'false') {
return false;
} else {
return str;
}
}
function fixBooleanLoose(str)
{
if (str === 'true' || str === '1' || str === 1) {
return true;
} else if (str === 'false' || str === '0' || str === 0) {
return false;
} else {
return str;
}
}
function generateMAC()
{
var data = [(Math.floor(Math.random() * 15) + 1).toString(16) + 2];
for (var i = 0; i < 5; i++) {
var oct = (Math.floor(Math.random() * 255) + 1).toString(16);
if (oct.length == 1) {
oct = '0' + oct;
}
data.push(oct);
}
return data.join(':');
}
function isPrivateIP(str)
{
if (!net.isIPv4(str)) {
return false;
}
function inRange(start, end, prospect) {
if (ipaddr.aton(start) <= ipaddr.aton(prospect)
&& ipaddr.aton(prospect) <= ipaddr.aton(end)) {
return true;
}
return false;
}
if (inRange('10.0.0.0', '10.255.255.255', str)) {
return true;
} else if (inRange('172.16.0.0', '172.31.255.255', str)) {
return true;
} else if (inRange('192.168.0.0', '192.168.255.255', str)) {
return true;
}
return false;
}
function isUUID(str)
{
var re = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
return typeof (str) === 'string' && re.test(str);
}
function validAttrValue(str)
{
var re = /^[a-zA-Z0-9\-\.\_]+$/;
// attempt to force value to string
switch (typeof (str)) {
case 'number':
str = str.toString();
break;
case 'string':
break;
default:
return false;
}
if (str && str.match(re)) {
return true;
} else {
return false;
}
}
function validResolver(str)
{
return Boolean(net.isIP(str));
}
function validMacAddress(str)
{
try {
macaddr.parse(str);
} catch (e) {
return false;
}
return /^[0-9a-fA-F:]+$/.test(str);
}
function validCID(cid)
{
return /^0x[0-9a-fA-F]+$/.test(cid)
&& (cid.length % 2) === 0
&& cid.length <= 512;
}
function ltrim(str, chars)
{
chars = chars || '\\s';
str = str || '';
return str.replace(new RegExp('^[' + chars + ']+', 'g'), '');
}
function numberify(str)
{
return Number(str);
}
function rtrim(str, chars)
{
chars = chars || '\\s';
str = str || '';
return str.replace(new RegExp('[' + chars + ']+$', 'g'), '');
}
function separateCommas(str)
{
return str.split(',');
}
function separateCommasAndNumberify(str)
{
var components = str.split(',');
var values = [];
components.forEach(function (component) {
values.push(numberify(component));
});
return values;
}
function trim(str, chars)
{
return ltrim(rtrim(str, chars), chars);
}
function unbase64(str)
{
return new Buffer(str, 'base64').toString('utf-8');
}
function unmangleMem(str)
{
return (Number(str) / (1024 * 1024));
}
// return the MAC address based on a VRRP Virtual Router ID
function vrrpMAC(vrid)
{
return sprintf('00:00:5e:00:01:%02x', vrid);
}
function isPowerOf2(val)
{
return val && !(val & (val - 1));
}
function validBhyveSectSize(str)
{
var sizes = str.split('/');
if (sizes.length === 0 || sizes.length > 2) {
return false;
}
var valid = sizes.filter(function validSize(x) {
return /^\d+$/.test(x) && isPowerOf2(Number(x));
});
return valid.length === sizes.length;
}
module.exports = {
addString: addString,
assertSafeZonePath: assertSafeZonePath,
epochTimestampSecs: epochTimestampSecs,
fixBoolean: fixBoolean,
fixBooleanLoose: fixBooleanLoose,
generateMAC: generateMAC,
isPrivateIP: isPrivateIP,
isUUID: isUUID,
ltrim: ltrim,
numberify: numberify,
rtrim: rtrim,
separateCommas: separateCommas,
separateCommasAndNumberify: separateCommasAndNumberify,
trim: trim,
unbase64: unbase64,
unmangleMem: unmangleMem,
validAttrValue: validAttrValue,
validBhyveSectSize: validBhyveSectSize,
validCID: validCID,
validResolver: validResolver,
validMacAddress: validMacAddress,
vrrpMAC: vrrpMAC
};
You can’t perform that action at this time.