Skip to content

Commit

Permalink
Merge pull request #36 from eflexsystems/v4
Browse files Browse the repository at this point in the history
  • Loading branch information
jakesjews authored Feb 9, 2021
2 parents 35ee0c0 + c45bbd3 commit 5bc3bba
Show file tree
Hide file tree
Showing 6 changed files with 3,864 additions and 1,311 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ node_modules

# JetBrains IDEs including IDEA and WebStorm
.idea/*

test.txt
12 changes: 3 additions & 9 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ const testFile = "test.txt";

const client = new SambaClient({
address: process.argv[2],
username: "Guest",
});

async function run() {
await fs.writeFile(testFile, testFile);

await client.mkdir("test-directory");
console.log(`created test directory on samba share at ${client.address}`);

const list = await client.listFiles("eflex", ".txt");
console.log(`found these files: ${list}`);

await client.mkdir("test-directory");
await client.mkdir("test directory");
console.log(`created test directory on samba share at ${client.address}`);

await client.sendFile(testFile, testFile);
Expand All @@ -36,10 +32,8 @@ async function run() {
} else {
console.log(`test file does not exist on samba share at ${client.address}`);
}
}

process.on("exit", function () {
fs.unlinkSync(testFile);
});
await fs.unlink(testFile);
}

run();
150 changes: 68 additions & 82 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use strict";

const exec = require("child_process").exec;
const util = require("util");
const execa = require("execa");
const p = require("path");

const singleSlash = /\//g;
Expand All @@ -11,15 +10,14 @@ const singleSlash = /\//g;
*/
const missingFileRegex = /(NT_STATUS_OBJECT_NAME_NOT_FOUND|NT_STATUS_NO_SUCH_FILE)/im;

function wrap(str) {
return "'" + str + "'";
}
const getCleanedSmbClientArgs = (args) =>
args.map((arg) => `"${arg.replace(singleSlash, "\\")}"`).join(" ");

class SambaClient {
constructor(options) {
this.address = options.address;
this.username = wrap(options.username || "guest");
this.password = options.password ? wrap(options.password) : null;
this.username = options.username || "guest";
this.password = options.password;
this.domain = options.domain;
this.port = options.port;
// Possible values for protocol version are listed in the Samba man pages:
Expand All @@ -28,35 +26,30 @@ class SambaClient {
this.maskCmd = Boolean(options.maskCmd);
}

getFile(path, destination, workingDir) {
const fileName = path.replace(singleSlash, "\\");
const cmdArgs = util.format("%s %s", fileName, destination);
return this.execute("get", cmdArgs, workingDir);
async getFile(path, destination, workingDir) {
return await this.execute("get", [path, destination], workingDir);
}

sendFile(path, destination) {
async sendFile(path, destination) {
const workingDir = p.dirname(path);
const fileName = p.basename(path).replace(singleSlash, "\\");
const cmdArgs = util.format(
"%s %s",
fileName,
destination.replace(singleSlash, "\\")
return await this.execute(
"put",
[p.basename(path), destination],
workingDir
);
return this.execute("put", cmdArgs, workingDir);
}

deleteFile(fileName) {
return this.execute("del", fileName, "");
async deleteFile(fileName) {
return await this.execute("del", [fileName], "");
}

async listFiles(fileNamePrefix, fileNameSuffix) {
try {
const cmdArgs = util.format("%s*%s", fileNamePrefix, fileNameSuffix);
const cmdArgs = `${fileNamePrefix}*${fileNameSuffix}`;
const allOutput = await this.execute("dir", cmdArgs, "");
const fileList = [];
const lines = allOutput.split("\n");
for (let i = 0; i < lines.length; i++) {
const line = lines[i].toString().trim();
for (let line of allOutput.split("\n")) {
line = line.toString().trim();
if (line.startsWith(fileNamePrefix)) {
const parsed = line.substring(
0,
Expand All @@ -75,20 +68,12 @@ class SambaClient {
}
}

mkdir(remotePath, cwd) {
return this.execute(
"mkdir",
remotePath.replace(singleSlash, "\\"),
cwd !== null && cwd !== undefined ? cwd : __dirname
);
async mkdir(remotePath, cwd) {
return await this.execute("mkdir", [remotePath], cwd || __dirname);
}

dir(remotePath, cwd) {
return this.execute(
"dir",
remotePath.replace(singleSlash, "\\"),
cwd !== null && cwd !== undefined ? cwd : __dirname
);
async dir(remotePath, cwd) {
return await this.execute("dir", [remotePath], cwd || __dirname);
}

async fileExists(remotePath, cwd) {
Expand All @@ -112,25 +97,35 @@ class SambaClient {
async list(remotePath) {
const remoteDirList = [];
const remoteDirContents = await this.dir(remotePath);
for(const content of remoteDirContents.matchAll(/\s*(.+?)\s{6,}(.)\s+([0-9]+)\s{2}(.+)/g)){
for (const content of remoteDirContents.matchAll(
/\s*(.+?)\s{6,}(.)\s+([0-9]+)\s{2}(.+)/g
)) {
remoteDirList.push({
name: content[1],
type: content[2],
size: parseInt(content[3]),
modifyTime: new Date(content[4]+'Z'),
modifyTime: new Date(content[4] + "Z"),
});
}
return remoteDirList;
}

getSmbClientArgs(fullCmd) {
const args = ["-U", this.username];
getSmbClientArgs(smbCommand, smbCommandArgs) {
const args = [];

if (this.username) {
args.push("-U", this.username);
}

if (!this.password) {
args.push("-N");
}

args.push("-c", fullCmd, this.address);
let cleanedSmbArgs = smbCommandArgs;
if (Array.isArray(smbCommandArgs)) {
cleanedSmbArgs = getCleanedSmbClientArgs(smbCommandArgs);
}
args.push("-c", `${smbCommand} ${cleanedSmbArgs}`, this.address);

if (this.password) {
args.push(this.password);
Expand All @@ -153,57 +148,48 @@ class SambaClient {
return args;
}

execute(cmd, cmdArgs, workingDir) {
const fullCmd = wrap(util.format("%s %s", cmd, cmdArgs));
const command = [
"smbclient",
this.getSmbClientArgs(fullCmd).join(" "),
].join(" ");
async execute(smbCommand, smbCommandArgs, workingDir) {
const args = this.getSmbClientArgs(smbCommand, smbCommandArgs);

const options = {
all: true,
cwd: workingDir || "",
};
const maskCmd = this.maskCmd;

return new Promise((resolve, reject) => {
exec(command, options, function (err, stdout, stderr) {
const allOutput = stdout + stderr;

if (err) {
// The error message by default contains the whole smbclient command that was run
// This contains the username, password in plain text which can be a security risk
// maskCmd option allows user to hide the command from the error message
err.message = maskCmd ? allOutput : err.message + allOutput;
return reject(err);
}

return resolve(allOutput);
});
});
try {
const { all } = await execa("smbclient", args, options);
return all;
} catch (error) {
if (this.maskCmd) {
error.message = error.all;
error.shortMessage = error.all;
}
throw error;
}
}

getAllShares() {
const maskCmd = this.maskCmd;
return new Promise((resolve, reject) => {
exec("smbtree -U guest -N", {}, function (err, stdout, stderr) {
const allOutput = stdout + stderr;

if (err !== null) {
err.message = maskCmd ? allOutput : err.message + allOutput;
return reject(err);
}
async getAllShares() {
try {
const { stdout } = await execa("smbtree", ["-U", "guest", "-N"], {
all: true,
});

const shares = [];
for (const line in stdout.split(/\r?\n/)) {
const words = line.split(/\t/);
if (words.length > 2 && words[2].match(/^\s*$/) !== null) {
shares.append(words[2].trim());
}
const shares = [];
for (const line in stdout.split(/\r?\n/)) {
const words = line.split(/\t/);
if (words.length > 2 && words[2].match(/^\s*$/) !== null) {
shares.append(words[2].trim());
}
}

return resolve(shares);
});
});
return shares;
} catch (error) {
if (this.maskCmd) {
error.message = error.all;
error.shortMessage = error.all;
}
throw error;
}
}
}

Expand Down
Loading

1 comment on commit 5bc3bba

@abergmann
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CVE-2021-27185 was assigned to this commit.

Please sign in to comment.