Skip to content

Commit

Permalink
allow getting acls of files
Browse files Browse the repository at this point in the history
  • Loading branch information
icewind1991 committed Feb 10, 2020
1 parent acd992a commit 3c5e45d
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
vendor
composer.lock
.php_cs.cache

listen.php
test.php
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
],
"require" : {
"php": ">=5.6",
"php": ">=7.1",
"icewind/streams": ">=0.2.0"
},
"require-dev": {
Expand Down
81 changes: 81 additions & 0 deletions src/ACL.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace Icewind\SMB;

class ACL {
const TYPE_ALLOW = 0;
const TYPE_DENY = 1;

const MASK_READ = 0x0001;
const MASK_WRITE = 0x0002;
const MASK_EXECUTE = 0x00020;
const MASK_DELETE = 0x10000;

const FLAG_OBJECT_INHERIT = 0x1;
const FLAG_CONTAINER_INHERIT = 0x2;

private $type;
private $flags;
private $mask;

public function __construct(int $type, int $flags, int $mask) {
$this->type = $type;
$this->flags = $flags;
$this->mask = $mask;
}

/**
* Check if the acl allows a specific permissions
*
* Note that this does not take inherited acls into account
*
* @param int $mask one of the ACL::MASK_* constants
* @return bool
*/
public function allows(int $mask): bool {
return $this->type === self::TYPE_ALLOW && ($this->mask & $mask) === $mask;
}

/**
* Check if the acl allows a specific permissions
*
* Note that this does not take inherited acls into account
*
* @param int $mask one of the ACL::MASK_* constants
* @return bool
*/
public function denies(int $mask): bool {
return $this->type === self::TYPE_DENY && ($this->mask & $mask) === $mask;
}

public function getType(): int {
return $this->type;
}

public function getFlags(): int {
return $this->flags;
}

public function getMask(): int {
return $this->mask;
}
}
5 changes: 5 additions & 0 deletions src/IFileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,9 @@ public function isSystem();
* @return bool
*/
public function isArchived();

/**
* @return ACL[]
*/
public function getAcls(): array;
}
7 changes: 7 additions & 0 deletions src/ISystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ public function getSmbclientPath();
*/
public function getNetPath();

/**
* Get the full path to the `smbcacls` binary of false if the binary is not available
*
* @return string|bool
*/
public function getSmbcAclsPath();

/**
* Get the full path to the `stdbuf` binary of false if the binary is not available
*
Expand Down
19 changes: 19 additions & 0 deletions src/Native/NativeFileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Icewind\SMB\Native;

use Icewind\SMB\ACL;
use Icewind\SMB\IFileInfo;

class NativeFileInfo implements IFileInfo {
Expand Down Expand Up @@ -156,4 +157,22 @@ public function isArchived() {
$mode = $this->getMode();
return (bool)($mode & IFileInfo::MODE_ARCHIVE);
}

/**
* @return ACL[]
*/
public function getAcls(): array {
$acls = [];
$attribute = $this->share->getAttribute($this->path, 'system.nt_sec_desc.acl.*+');

foreach (explode(',', $attribute) as $acl) {
[$user, $permissions] = explode(':', $acl, 2);
[$type, $flags, $mask] = explode('/', $permissions);
$mask = hexdec($mask);

$acls[$user] = new ACL($type, $flags, $mask);
}

return $acls;
}
}
4 changes: 4 additions & 0 deletions src/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public function getNetPath() {
return $this->getBinaryPath('net');
}

public function getSmbcAclsPath() {
return $this->getBinaryPath('smbcacls');
}

public function getStdBufPath() {
return $this->getBinaryPath('stdbuf');
}
Expand Down
17 changes: 16 additions & 1 deletion src/Wrapped/FileInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

namespace Icewind\SMB\Wrapped;

use Icewind\SMB\ACL;
use Icewind\SMB\IFileInfo;

class FileInfo implements IFileInfo {
Expand Down Expand Up @@ -35,19 +36,26 @@ class FileInfo implements IFileInfo {
*/
protected $mode;

/**
* @var callable
*/
protected $aclCallback;

/**
* @param string $path
* @param string $name
* @param int $size
* @param int $time
* @param int $mode
* @param callable $aclCallback
*/
public function __construct($path, $name, $size, $time, $mode) {
public function __construct($path, $name, $size, $time, $mode, callable $aclCallback) {
$this->path = $path;
$this->name = $name;
$this->size = $size;
$this->time = $time;
$this->mode = $mode;
$this->aclCallback = $aclCallback;
}

/**
Expand Down Expand Up @@ -112,4 +120,11 @@ public function isSystem() {
public function isArchived() {
return (bool)($this->mode & IFileInfo::MODE_ARCHIVE);
}

/**
* @return ACL[]
*/
public function getAcls(): array {
return ($this->aclCallback)();
}
}
7 changes: 5 additions & 2 deletions src/Wrapped/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public function parseStat($output) {
];
}

public function parseDir($output, $basePath) {
public function parseDir($output, $basePath, callable $aclCallback) {
//last line is used space
array_pop($output);
$regex = '/^\s*(.*?)\s\s\s\s+(?:([NDHARS]*)\s+)?([0-9]+)\s+(.*)$/';
Expand All @@ -162,7 +162,10 @@ public function parseDir($output, $basePath) {
if ($name !== '.' and $name !== '..') {
$mode = $this->parseMode($mode);
$time = strtotime($time . ' ' . $this->timeZone);
$content[] = new FileInfo($basePath . '/' . $name, $name, $size, $time, $mode);
$path = $basePath . '/' . $name;
$content[] = new FileInfo($path, $name, $size, $time, $mode, function () use ($aclCallback, $path) {
return $aclCallback($path);
});
}
}
}
Expand Down
91 changes: 87 additions & 4 deletions src/Wrapped/Share.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Icewind\SMB\Wrapped;

use Icewind\SMB\AbstractShare;
use Icewind\SMB\ACL;
use Icewind\SMB\Exception\ConnectionException;
use Icewind\SMB\Exception\DependencyException;
use Icewind\SMB\Exception\FileInUseException;
Expand Down Expand Up @@ -153,7 +154,9 @@ public function dir($path) {

$this->execute('cd /');

return $this->parser->parseDir($output, $path);
return $this->parser->parseDir($output, $path, function ($path) {
return $this->getAcls($path);
});
}

/**
Expand Down Expand Up @@ -186,7 +189,9 @@ public function stat($path) {
$this->parseOutput($output, $path);
}
$stat = $this->parser->parseStat($output);
return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode']);
return new FileInfo($path, basename($path), $stat['size'], $stat['mtime'], $stat['mode'], function () use ($path) {
return $this->getAcls($path);
});
}

/**
Expand Down Expand Up @@ -421,13 +426,13 @@ protected function execute($command) {
* @param string[] $lines
* @param string $path
*
* @throws NotFoundException
* @return bool
* @throws \Icewind\SMB\Exception\AlreadyExistsException
* @throws \Icewind\SMB\Exception\AccessDeniedException
* @throws \Icewind\SMB\Exception\NotEmptyException
* @throws \Icewind\SMB\Exception\InvalidTypeException
* @throws \Icewind\SMB\Exception\Exception
* @return bool
* @throws NotFoundException
*/
protected function parseOutput($lines, $path = '') {
if (count($lines) === 0) {
Expand Down Expand Up @@ -470,6 +475,84 @@ protected function escapeLocalPath($path) {
return '"' . $path . '"';
}

protected function getAcls($path) {
$commandPath = $this->system->getSmbcAclsPath();
if (!$commandPath) {
return [];
}

$command = sprintf(
'%s %s %s %s/%s %s',
$commandPath,
$this->getAuthFileArgument(),
$this->server->getAuth()->getExtraCommandLineArguments(),
escapeshellarg('//' . $this->server->getHost()),
escapeshellarg($this->name),
escapeshellarg($path)
);
$connection = new RawConnection($command);
$connection->writeAuthentication($this->server->getAuth()->getUsername(), $this->server->getAuth()->getPassword());
$connection->connect();
if (!$connection->isValid()) {
throw new ConnectionException($connection->readLine());
}

$rawAcls = $connection->readAll();
var_dump($rawAcls);

$acls = [];
foreach ($rawAcls as $acl) {
[$type, $acl] = explode(':', $acl, 2);
if ($type !== 'ACL') {
continue;
}
[$user, $permissions] = explode(':', $acl, 2);
[$type, $flags, $mask] = explode('/', $permissions);

$type = $type === 'ALLOWED' ? ACL::TYPE_ALLOW : ACL::TYPE_DENY;

$flagsInt = 0;
foreach (explode('|', $flags) as $flagString) {
if ($flagString === 'OI') {
$flagsInt += ACL::FLAG_OBJECT_INHERIT;
} elseif ($flagString === 'CI') {
$flagsInt += ACL::FLAG_CONTAINER_INHERIT;
}
}

if (substr($mask, 0, 2) === '0x') {
$maskInt = hexdec($mask);
} else {
$maskInt = 0;
foreach (explode('|', $mask) as $maskString) {
if ($maskString === 'R') {
$maskInt += ACL::MASK_READ;
} elseif ($maskString === 'W') {
$maskInt += ACL::MASK_WRITE;
} elseif ($maskString === 'X') {
$maskInt += ACL::MASK_EXECUTE;
} elseif ($maskString === 'D') {
$maskInt += ACL::MASK_DELETE;
} elseif ($maskString === 'READ') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE;
} elseif ($maskString === 'CHANGE') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
} elseif ($maskString === 'FULL') {
$maskInt += ACL::MASK_READ + ACL::MASK_EXECUTE + ACL::MASK_WRITE + ACL::MASK_DELETE;
}
}
}

if (isset($acls[$user])) {
$existing = $acls[$user];
$maskInt += $existing->getMask();
}
$acls[$user] = new ACL($type, $flagsInt, $maskInt);
}

return $acls;
}

public function __destruct() {
unset($this->connection);
}
Expand Down
4 changes: 2 additions & 2 deletions tests/AbstractShareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ public function testDir() {
}
$this->assertTrue($dirEntry->isDirectory());
$this->assertFalse($dirEntry->isReadOnly());
$this->assertFalse($dirEntry->isReadOnly());
$this->assertFalse($dirEntry->isHidden());

if ($dir[0]->getName() === 'file.txt') {
$fileEntry = $dir[0];
Expand All @@ -525,7 +525,7 @@ public function testDir() {
}
$this->assertFalse($fileEntry->isDirectory());
$this->assertFalse($fileEntry->isReadOnly());
$this->assertFalse($fileEntry->isReadOnly());
$this->assertFalse($fileEntry->isHidden());
}

/**
Expand Down
Loading

0 comments on commit 3c5e45d

Please sign in to comment.