Signed-off-by: Robin Appelman <robin@icewind.nl>tags/v24.0.0beta1
@@ -5,6 +5,8 @@ icewind/smb/install_libsmbclient.sh | |||
icewind/smb/Makefile | |||
icewind/smb/.travis.yml | |||
icewind/smb/.scrutinizer.yml | |||
icewind/smb/example-apache-kerberos.php | |||
icewind/smb/codecov.yml | |||
icewind/streams/tests | |||
.github | |||
.php_cs* |
@@ -9,6 +9,6 @@ | |||
}, | |||
"require": { | |||
"icewind/streams": "0.7.4", | |||
"icewind/smb": "3.4.1" | |||
"icewind/smb": "3.5.1" | |||
} | |||
} |
@@ -4,20 +4,20 @@ | |||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | |||
"This file is @generated automatically" | |||
], | |||
"content-hash": "0ffc772b2aaaaffe52decb8d13361976", | |||
"content-hash": "ed821b15824934fd2d245faca1f35aad", | |||
"packages": [ | |||
{ | |||
"name": "icewind/smb", | |||
"version": "v3.4.1", | |||
"version": "v3.5.1", | |||
"source": { | |||
"type": "git", | |||
"url": "https://github.com/icewind1991/SMB.git", | |||
"reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3" | |||
"reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563" | |||
}, | |||
"dist": { | |||
"type": "zip", | |||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/9dba42ab2a3990de29e18cc62b0a8270aceb74e3", | |||
"reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3", | |||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", | |||
"reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", | |||
"shasum": "" | |||
}, | |||
"require": { | |||
@@ -49,9 +49,9 @@ | |||
"description": "php wrapper for smbclient and libsmbclient-php", | |||
"support": { | |||
"issues": "https://github.com/icewind1991/SMB/issues", | |||
"source": "https://github.com/icewind1991/SMB/tree/v3.4.1" | |||
"source": "https://github.com/icewind1991/SMB/tree/v3.5.1" | |||
}, | |||
"time": "2021-04-19T13:53:08+00:00" | |||
"time": "2021-11-04T14:28:18+00:00" | |||
}, | |||
{ | |||
"name": "icewind/streams", |
@@ -42,30 +42,75 @@ namespace Composer\Autoload; | |||
*/ | |||
class ClassLoader | |||
{ | |||
/** @var ?string */ | |||
private $vendorDir; | |||
// PSR-4 | |||
/** | |||
* @var array[] | |||
* @psalm-var array<string, array<string, int>> | |||
*/ | |||
private $prefixLengthsPsr4 = array(); | |||
/** | |||
* @var array[] | |||
* @psalm-var array<string, array<int, string>> | |||
*/ | |||
private $prefixDirsPsr4 = array(); | |||
/** | |||
* @var array[] | |||
* @psalm-var array<string, string> | |||
*/ | |||
private $fallbackDirsPsr4 = array(); | |||
// PSR-0 | |||
/** | |||
* @var array[] | |||
* @psalm-var array<string, array<string, string[]>> | |||
*/ | |||
private $prefixesPsr0 = array(); | |||
/** | |||
* @var array[] | |||
* @psalm-var array<string, string> | |||
*/ | |||
private $fallbackDirsPsr0 = array(); | |||
/** @var bool */ | |||
private $useIncludePath = false; | |||
/** | |||
* @var string[] | |||
* @psalm-var array<string, string> | |||
*/ | |||
private $classMap = array(); | |||
/** @var bool */ | |||
private $classMapAuthoritative = false; | |||
/** | |||
* @var bool[] | |||
* @psalm-var array<string, bool> | |||
*/ | |||
private $missingClasses = array(); | |||
/** @var ?string */ | |||
private $apcuPrefix; | |||
/** | |||
* @var self[] | |||
*/ | |||
private static $registeredLoaders = array(); | |||
/** | |||
* @param ?string $vendorDir | |||
*/ | |||
public function __construct($vendorDir = null) | |||
{ | |||
$this->vendorDir = $vendorDir; | |||
} | |||
/** | |||
* @return string[] | |||
*/ | |||
public function getPrefixes() | |||
{ | |||
if (!empty($this->prefixesPsr0)) { | |||
@@ -75,28 +120,47 @@ class ClassLoader | |||
return array(); | |||
} | |||
/** | |||
* @return array[] | |||
* @psalm-return array<string, array<int, string>> | |||
*/ | |||
public function getPrefixesPsr4() | |||
{ | |||
return $this->prefixDirsPsr4; | |||
} | |||
/** | |||
* @return array[] | |||
* @psalm-return array<string, string> | |||
*/ | |||
public function getFallbackDirs() | |||
{ | |||
return $this->fallbackDirsPsr0; | |||
} | |||
/** | |||
* @return array[] | |||
* @psalm-return array<string, string> | |||
*/ | |||
public function getFallbackDirsPsr4() | |||
{ | |||
return $this->fallbackDirsPsr4; | |||
} | |||
/** | |||
* @return string[] Array of classname => path | |||
* @psalm-var array<string, string> | |||
*/ | |||
public function getClassMap() | |||
{ | |||
return $this->classMap; | |||
} | |||
/** | |||
* @param array $classMap Class to filename map | |||
* @param string[] $classMap Class to filename map | |||
* @psalm-param array<string, string> $classMap | |||
* | |||
* @return void | |||
*/ | |||
public function addClassMap(array $classMap) | |||
{ | |||
@@ -111,9 +175,11 @@ class ClassLoader | |||
* Registers a set of PSR-0 directories for a given prefix, either | |||
* appending or prepending to the ones previously set for this prefix. | |||
* | |||
* @param string $prefix The prefix | |||
* @param array|string $paths The PSR-0 root directories | |||
* @param bool $prepend Whether to prepend the directories | |||
* @param string $prefix The prefix | |||
* @param string[]|string $paths The PSR-0 root directories | |||
* @param bool $prepend Whether to prepend the directories | |||
* | |||
* @return void | |||
*/ | |||
public function add($prefix, $paths, $prepend = false) | |||
{ | |||
@@ -156,11 +222,13 @@ class ClassLoader | |||
* Registers a set of PSR-4 directories for a given namespace, either | |||
* appending or prepending to the ones previously set for this namespace. | |||
* | |||
* @param string $prefix The prefix/namespace, with trailing '\\' | |||
* @param array|string $paths The PSR-4 base directories | |||
* @param bool $prepend Whether to prepend the directories | |||
* @param string $prefix The prefix/namespace, with trailing '\\' | |||
* @param string[]|string $paths The PSR-4 base directories | |||
* @param bool $prepend Whether to prepend the directories | |||
* | |||
* @throws \InvalidArgumentException | |||
* | |||
* @return void | |||
*/ | |||
public function addPsr4($prefix, $paths, $prepend = false) | |||
{ | |||
@@ -204,8 +272,10 @@ class ClassLoader | |||
* Registers a set of PSR-0 directories for a given prefix, | |||
* replacing any others previously set for this prefix. | |||
* | |||
* @param string $prefix The prefix | |||
* @param array|string $paths The PSR-0 base directories | |||
* @param string $prefix The prefix | |||
* @param string[]|string $paths The PSR-0 base directories | |||
* | |||
* @return void | |||
*/ | |||
public function set($prefix, $paths) | |||
{ | |||
@@ -220,10 +290,12 @@ class ClassLoader | |||
* Registers a set of PSR-4 directories for a given namespace, | |||
* replacing any others previously set for this namespace. | |||
* | |||
* @param string $prefix The prefix/namespace, with trailing '\\' | |||
* @param array|string $paths The PSR-4 base directories | |||
* @param string $prefix The prefix/namespace, with trailing '\\' | |||
* @param string[]|string $paths The PSR-4 base directories | |||
* | |||
* @throws \InvalidArgumentException | |||
* | |||
* @return void | |||
*/ | |||
public function setPsr4($prefix, $paths) | |||
{ | |||
@@ -243,6 +315,8 @@ class ClassLoader | |||
* Turns on searching the include path for class files. | |||
* | |||
* @param bool $useIncludePath | |||
* | |||
* @return void | |||
*/ | |||
public function setUseIncludePath($useIncludePath) | |||
{ | |||
@@ -265,6 +339,8 @@ class ClassLoader | |||
* that have not been registered with the class map. | |||
* | |||
* @param bool $classMapAuthoritative | |||
* | |||
* @return void | |||
*/ | |||
public function setClassMapAuthoritative($classMapAuthoritative) | |||
{ | |||
@@ -285,6 +361,8 @@ class ClassLoader | |||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled. | |||
* | |||
* @param string|null $apcuPrefix | |||
* | |||
* @return void | |||
*/ | |||
public function setApcuPrefix($apcuPrefix) | |||
{ | |||
@@ -305,6 +383,8 @@ class ClassLoader | |||
* Registers this instance as an autoloader. | |||
* | |||
* @param bool $prepend Whether to prepend the autoloader or not | |||
* | |||
* @return void | |||
*/ | |||
public function register($prepend = false) | |||
{ | |||
@@ -324,6 +404,8 @@ class ClassLoader | |||
/** | |||
* Unregisters this instance as an autoloader. | |||
* | |||
* @return void | |||
*/ | |||
public function unregister() | |||
{ | |||
@@ -403,6 +485,11 @@ class ClassLoader | |||
return self::$registeredLoaders; | |||
} | |||
/** | |||
* @param string $class | |||
* @param string $ext | |||
* @return string|false | |||
*/ | |||
private function findFileWithExtension($class, $ext) | |||
{ | |||
// PSR-4 lookup | |||
@@ -474,6 +561,10 @@ class ClassLoader | |||
* Scope isolated include. | |||
* | |||
* Prevents access to $this/self from included files. | |||
* | |||
* @param string $file | |||
* @return void | |||
* @private | |||
*/ | |||
function includeFile($file) | |||
{ |
@@ -20,12 +20,25 @@ use Composer\Semver\VersionParser; | |||
* | |||
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions | |||
* | |||
* To require it's presence, you can require `composer-runtime-api ^2.0` | |||
* To require its presence, you can require `composer-runtime-api ^2.0` | |||
*/ | |||
class InstalledVersions | |||
{ | |||
/** | |||
* @var mixed[]|null | |||
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null | |||
*/ | |||
private static $installed; | |||
/** | |||
* @var bool|null | |||
*/ | |||
private static $canGetVendors; | |||
/** | |||
* @var array[] | |||
* @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> | |||
*/ | |||
private static $installedByVendor = array(); | |||
/** | |||
@@ -228,7 +241,7 @@ class InstalledVersions | |||
/** | |||
* @return array | |||
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string} | |||
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} | |||
*/ | |||
public static function getRootPackage() | |||
{ | |||
@@ -242,7 +255,7 @@ class InstalledVersions | |||
* | |||
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. | |||
* @return array[] | |||
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} | |||
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} | |||
*/ | |||
public static function getRawData() | |||
{ | |||
@@ -265,7 +278,7 @@ class InstalledVersions | |||
* Returns the raw data of all installed.php which are currently loaded for custom implementations | |||
* | |||
* @return array[] | |||
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> | |||
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> | |||
*/ | |||
public static function getAllRawData() | |||
{ | |||
@@ -288,7 +301,7 @@ class InstalledVersions | |||
* @param array[] $data A vendor/composer/installed.php data set | |||
* @return void | |||
* | |||
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data | |||
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data | |||
*/ | |||
public static function reload($data) | |||
{ | |||
@@ -298,7 +311,7 @@ class InstalledVersions | |||
/** | |||
* @return array[] | |||
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}> | |||
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> | |||
*/ | |||
private static function getInstalled() | |||
{ |
@@ -48,6 +48,7 @@ return array( | |||
'Icewind\\SMB\\IShare' => $vendorDir . '/icewind/smb/src/IShare.php', | |||
'Icewind\\SMB\\ISystem' => $vendorDir . '/icewind/smb/src/ISystem.php', | |||
'Icewind\\SMB\\ITimeZoneProvider' => $vendorDir . '/icewind/smb/src/ITimeZoneProvider.php', | |||
'Icewind\\SMB\\KerberosApacheAuth' => $vendorDir . '/icewind/smb/src/KerberosApacheAuth.php', | |||
'Icewind\\SMB\\KerberosAuth' => $vendorDir . '/icewind/smb/src/KerberosAuth.php', | |||
'Icewind\\SMB\\Native\\NativeFileInfo' => $vendorDir . '/icewind/smb/src/Native/NativeFileInfo.php', | |||
'Icewind\\SMB\\Native\\NativeReadStream' => $vendorDir . '/icewind/smb/src/Native/NativeReadStream.php', |
@@ -68,6 +68,7 @@ class ComposerStaticInit98fe9b281934250b3a93f69a5ce843b3 | |||
'Icewind\\SMB\\IShare' => __DIR__ . '/..' . '/icewind/smb/src/IShare.php', | |||
'Icewind\\SMB\\ISystem' => __DIR__ . '/..' . '/icewind/smb/src/ISystem.php', | |||
'Icewind\\SMB\\ITimeZoneProvider' => __DIR__ . '/..' . '/icewind/smb/src/ITimeZoneProvider.php', | |||
'Icewind\\SMB\\KerberosApacheAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosApacheAuth.php', | |||
'Icewind\\SMB\\KerberosAuth' => __DIR__ . '/..' . '/icewind/smb/src/KerberosAuth.php', | |||
'Icewind\\SMB\\Native\\NativeFileInfo' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeFileInfo.php', | |||
'Icewind\\SMB\\Native\\NativeReadStream' => __DIR__ . '/..' . '/icewind/smb/src/Native/NativeReadStream.php', |
@@ -2,17 +2,17 @@ | |||
"packages": [ | |||
{ | |||
"name": "icewind/smb", | |||
"version": "v3.4.1", | |||
"version_normalized": "3.4.1.0", | |||
"version": "v3.5.1", | |||
"version_normalized": "3.5.1.0", | |||
"source": { | |||
"type": "git", | |||
"url": "https://github.com/icewind1991/SMB.git", | |||
"reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3" | |||
"reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563" | |||
}, | |||
"dist": { | |||
"type": "zip", | |||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/9dba42ab2a3990de29e18cc62b0a8270aceb74e3", | |||
"reference": "9dba42ab2a3990de29e18cc62b0a8270aceb74e3", | |||
"url": "https://api.github.com/repos/icewind1991/SMB/zipball/c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", | |||
"reference": "c1ce4fbb2ff1786846d9d0b3850b395ca94cf563", | |||
"shasum": "" | |||
}, | |||
"require": { | |||
@@ -25,7 +25,7 @@ | |||
"phpunit/phpunit": "^8.5|^9.3.8", | |||
"psalm/phar": "^4.3" | |||
}, | |||
"time": "2021-04-19T13:53:08+00:00", | |||
"time": "2021-11-04T14:28:18+00:00", | |||
"type": "library", | |||
"installation-source": "dist", | |||
"autoload": { | |||
@@ -46,7 +46,7 @@ | |||
"description": "php wrapper for smbclient and libsmbclient-php", | |||
"support": { | |||
"issues": "https://github.com/icewind1991/SMB/issues", | |||
"source": "https://github.com/icewind1991/SMB/tree/v3.4.1" | |||
"source": "https://github.com/icewind1991/SMB/tree/v3.5.1" | |||
}, | |||
"install-path": "../icewind/smb" | |||
}, |
@@ -5,7 +5,7 @@ | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../', | |||
'aliases' => array(), | |||
'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4', | |||
'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec', | |||
'name' => 'files_external/3rdparty', | |||
'dev' => true, | |||
), | |||
@@ -16,16 +16,16 @@ | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../', | |||
'aliases' => array(), | |||
'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4', | |||
'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec', | |||
'dev_requirement' => false, | |||
), | |||
'icewind/smb' => array( | |||
'pretty_version' => 'v3.4.1', | |||
'version' => '3.4.1.0', | |||
'pretty_version' => 'v3.5.1', | |||
'version' => '3.5.1.0', | |||
'type' => 'library', | |||
'install_path' => __DIR__ . '/../icewind/smb', | |||
'aliases' => array(), | |||
'reference' => '9dba42ab2a3990de29e18cc62b0a8270aceb74e3', | |||
'reference' => 'c1ce4fbb2ff1786846d9d0b3850b395ca94cf563', | |||
'dev_requirement' => false, | |||
), | |||
'icewind/streams' => array( |
@@ -44,13 +44,42 @@ $server = $serverFactory->createServer('localhost', $auth); | |||
### Using kerberos authentication ### | |||
There are two ways of using kerberos to authenticate against the smb server: | |||
- Using a ticket from the php server | |||
- Re-using a ticket send by the client | |||
### Using a server ticket | |||
Using a server ticket allows the web server to authenticate against the smb server using an existing machine account. | |||
The ticket needs to be available in the environment of the php process. | |||
```php | |||
$serverFactory = new ServerFactory(); | |||
$auth = new KerberosAuth(); | |||
$server = $serverFactory->createServer('localhost', $auth); | |||
``` | |||
Note that this requires a valid kerberos ticket to already be available for php | |||
### Re-using a client ticket | |||
By re-using a client ticket you can create a single sign-on setup where the user authenticates against | |||
the web service using kerberos. And the web server can forward that ticket to the smb server, allowing it | |||
to act on the behalf of the user without requiring the user to enter his passord. | |||
The setup for such a system is fairly involved and requires roughly the following this | |||
- The web server is authenticated against kerberos with a machine account | |||
- Delegation is enabled for the web server's machine account | |||
- Apache is setup to perform kerberos authentication and save the ticket in it's environment | |||
- Php has the krb5 extension installed | |||
- The client authenticates using a ticket with forwarding enabled | |||
```php | |||
$serverFactory = new ServerFactory(); | |||
$auth = new KerberosApacheAuth(); | |||
$server = $serverFactory->createServer('localhost', $auth); | |||
``` | |||
### Upload a file ### | |||
@@ -45,7 +45,7 @@ interface IShare { | |||
public function put(string $source, string $target): bool; | |||
/** | |||
* Open a readable stream top a remote file | |||
* Open a readable stream to a remote file | |||
* | |||
* @param string $source | |||
* @return resource a read only stream with the contents of the remote file |
@@ -0,0 +1,117 @@ | |||
<?php | |||
/** | |||
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> | |||
* | |||
* @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; | |||
use Icewind\SMB\Exception\DependencyException; | |||
use Icewind\SMB\Exception\Exception; | |||
/** | |||
* Use existing kerberos ticket to authenticate and reuse the apache ticket cache (mod_auth_kerb) | |||
*/ | |||
class KerberosApacheAuth extends KerberosAuth implements IAuth { | |||
/** @var string */ | |||
private $ticketPath = ""; | |||
// only working with specific library (mod_auth_kerb, krb5, smbclient) versions | |||
/** @var bool */ | |||
private $saveTicketInMemory = false; | |||
/** @var bool */ | |||
private $init = false; | |||
/** | |||
* @param bool $saveTicketInMemory | |||
*/ | |||
public function __construct(bool $saveTicketInMemory = false) { | |||
$this->saveTicketInMemory = $saveTicketInMemory; | |||
} | |||
/** | |||
* Check if a valid kerberos ticket is present | |||
* | |||
* @return bool | |||
*/ | |||
public function checkTicket(): bool { | |||
//read apache kerberos ticket cache | |||
$cacheFile = getenv("KRB5CCNAME"); | |||
if (!$cacheFile) { | |||
return false; | |||
} | |||
$krb5 = new \KRB5CCache(); | |||
$krb5->open($cacheFile); | |||
return (bool)$krb5->isValid(); | |||
} | |||
private function init(): void { | |||
if ($this->init) { | |||
return; | |||
} | |||
$this->init = true; | |||
// inspired by https://git.typo3.org/TYPO3CMS/Extensions/fal_cifs.git | |||
if (!extension_loaded("krb5")) { | |||
// https://pecl.php.net/package/krb5 | |||
throw new DependencyException('Ensure php-krb5 is installed.'); | |||
} | |||
//read apache kerberos ticket cache | |||
$cacheFile = getenv("KRB5CCNAME"); | |||
if (!$cacheFile) { | |||
throw new Exception('No kerberos ticket cache environment variable (KRB5CCNAME) found.'); | |||
} | |||
$krb5 = new \KRB5CCache(); | |||
$krb5->open($cacheFile); | |||
if (!$krb5->isValid()) { | |||
throw new Exception('Kerberos ticket cache is not valid.'); | |||
} | |||
if ($this->saveTicketInMemory) { | |||
putenv("KRB5CCNAME=" . (string)$krb5->getName()); | |||
} else { | |||
//workaround: smbclient is not working with the original apache ticket cache. | |||
$tmpFilename = tempnam("/tmp", "krb5cc_php_"); | |||
$tmpCacheFile = "FILE:" . $tmpFilename; | |||
$krb5->save($tmpCacheFile); | |||
$this->ticketPath = $tmpFilename; | |||
putenv("KRB5CCNAME=" . $tmpCacheFile); | |||
} | |||
} | |||
public function getExtraCommandLineArguments(): string { | |||
$this->init(); | |||
return parent::getExtraCommandLineArguments(); | |||
} | |||
public function setExtraSmbClientOptions($smbClientState): void { | |||
$this->init(); | |||
parent::setExtraSmbClientOptions($smbClientState); | |||
} | |||
public function __destruct() { | |||
if (!empty($this->ticketPath) && file_exists($this->ticketPath) && is_file($this->ticketPath)) { | |||
unlink($this->ticketPath); | |||
} | |||
} | |||
} |
@@ -99,7 +99,7 @@ class NativeFileInfo implements IFileInfo { | |||
public function isDirectory(): bool { | |||
$mode = $this->getMode(); | |||
if ($mode > 0x1000) { | |||
return (bool)($mode & 0x4000); // 0x4000: unix directory flag | |||
return ($mode & 0x4000 && !($mode & 0x8000)); // 0x4000: unix directory flag shares bits with 0xC000: socket | |||
} else { | |||
return (bool)($mode & IFileInfo::MODE_DIRECTORY); | |||
} |
@@ -267,14 +267,14 @@ class NativeShare extends AbstractShare { | |||
* Open a writeable stream to a remote file | |||
* Note: This method will truncate the file to 0bytes first | |||
* | |||
* @param string $source | |||
* @param string $target | |||
* @return resource a writeable stream | |||
* | |||
* @throws NotFoundException | |||
* @throws InvalidTypeException | |||
*/ | |||
public function write(string $source) { | |||
$url = $this->buildUrl($source); | |||
public function write(string $target) { | |||
$url = $this->buildUrl($target); | |||
$handle = $this->getState()->create($url); | |||
return NativeWriteStream::wrap($this->getState(), $handle, 'w', $url); | |||
} | |||
@@ -282,14 +282,14 @@ class NativeShare extends AbstractShare { | |||
/** | |||
* Open a writeable stream and set the cursor to the end of the stream | |||
* | |||
* @param string $source | |||
* @param string $target | |||
* @return resource a writeable stream | |||
* | |||
* @throws NotFoundException | |||
* @throws InvalidTypeException | |||
*/ | |||
public function append(string $source) { | |||
$url = $this->buildUrl($source); | |||
public function append(string $target) { | |||
$url = $this->buildUrl($target); | |||
$handle = $this->getState()->open($url, "a+"); | |||
return NativeWriteStream::wrap($this->getState(), $handle, "a", $url); | |||
} |
@@ -38,6 +38,14 @@ class NativeState { | |||
/** @var bool */ | |||
protected $connected = false; | |||
/** | |||
* sync the garbage collection cycle | |||
* __deconstruct() of KerberosAuth should not called too soon | |||
* | |||
* @var IAuth|null $auth | |||
*/ | |||
protected $auth = null; | |||
// see error.h | |||
const EXCEPTION_MAP = [ | |||
1 => ForbiddenException::class, | |||
@@ -107,6 +115,11 @@ class NativeState { | |||
} | |||
$auth->setExtraSmbClientOptions($this->state); | |||
// sync the garbage collection cycle | |||
// __deconstruct() of KerberosAuth should not caled too soon | |||
$this->auth = $auth; | |||
/** @var bool $result */ | |||
$result = @smbclient_state_init($this->state, $auth->getWorkgroup(), $auth->getUsername(), $auth->getPassword()); | |||
@@ -62,7 +62,7 @@ class System implements ISystem { | |||
$result = null; | |||
$output = []; | |||
exec("which $binary 2>&1", $output, $result); | |||
$this->paths[$binary] = $result === 0 ? trim(implode('', $output)) : null; | |||
$this->paths[$binary] = $result === 0 && isset($output[0]) ? (string)$output[0] : null; | |||
} | |||
return $this->paths[$binary]; | |||
} |
@@ -47,7 +47,7 @@ class Connection extends RawConnection { | |||
public function clearTillPrompt(): void { | |||
$this->write(''); | |||
do { | |||
$promptLine = $this->readLine(); | |||
$promptLine = $this->readTillPrompt(); | |||
if ($promptLine === false) { | |||
break; | |||
} | |||
@@ -56,13 +56,12 @@ class Connection extends RawConnection { | |||
if ($this->write('') === false) { | |||
throw new ConnectionRefusedException(); | |||
} | |||
$this->readLine(); | |||
$this->readTillPrompt(); | |||
} | |||
/** | |||
* get all unprocessed output from smbclient until the next prompt | |||
* | |||
* @param (callable(string):bool)|null $callback (optional) callback to call for every line read | |||
* @return string[] | |||
* @throws AuthenticationException | |||
* @throws ConnectException | |||
@@ -71,42 +70,22 @@ class Connection extends RawConnection { | |||
* @throws NoLoginServerException | |||
* @throws AccessDeniedException | |||
*/ | |||
public function read(callable $callback = null): array { | |||
public function read(): array { | |||
if (!$this->isValid()) { | |||
throw new ConnectionException('Connection not valid'); | |||
} | |||
$promptLine = $this->readLine(); //first line is prompt | |||
if ($promptLine === false) { | |||
$this->unknownError($promptLine); | |||
} | |||
$this->parser->checkConnectionError($promptLine); | |||
$output = []; | |||
if (!$this->isPrompt($promptLine)) { | |||
$line = $promptLine; | |||
} else { | |||
$line = $this->readLine(); | |||
} | |||
if ($line === false) { | |||
$this->unknownError($promptLine); | |||
} | |||
while ($line !== false && !$this->isPrompt($line)) { //next prompt functions as delimiter | |||
if (is_callable($callback)) { | |||
$result = $callback($line); | |||
if ($result === false) { // allow the callback to close the connection for infinite running commands | |||
$this->close(true); | |||
break; | |||
} | |||
} else { | |||
$output[] = $line; | |||
} | |||
$line = $this->readLine(); | |||
$output = $this->readTillPrompt(); | |||
if ($output === false) { | |||
$this->unknownError(false); | |||
} | |||
$output = explode("\n", $output); | |||
// last line contains the prompt | |||
array_pop($output); | |||
return $output; | |||
} | |||
private function isPrompt(string $line): bool { | |||
return mb_substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; | |||
return substr($line, 0, self::DELIMITER_LENGTH) === self::DELIMITER; | |||
} | |||
/** | |||
@@ -132,6 +111,6 @@ class Connection extends RawConnection { | |||
// ignore any errors while trying to send the close command, the process might already be dead | |||
@$this->write('close' . PHP_EOL); | |||
} | |||
parent::close($terminate); | |||
$this->close_process($terminate); | |||
} | |||
} |
@@ -65,16 +65,20 @@ class NotifyHandler implements INotifyHandler { | |||
*/ | |||
public function listen(callable $callback): void { | |||
if ($this->listening) { | |||
$this->connection->read(function (string $line) use ($callback): bool { | |||
while (true) { | |||
$line = $this->connection->readLine(); | |||
if ($line === false) { | |||
break; | |||
} | |||
$this->checkForError($line); | |||
$change = $this->parseChangeLine($line); | |||
if ($change) { | |||
$result = $callback($change); | |||
return $result === false ? false : true; | |||
} else { | |||
return true; | |||
if ($result === false) { | |||
break; | |||
} | |||
} | |||
}); | |||
}; | |||
} | |||
} | |||
@@ -71,7 +71,8 @@ class RawConnection { | |||
setlocale(LC_ALL, Server::LOCALE); | |||
$env = array_merge($this->env, [ | |||
'CLI_FORCE_INTERACTIVE' => 'y', // Needed or the prompt isn't displayed!! | |||
'CLI_FORCE_INTERACTIVE' => 'y', // Make sure the prompt is displayed | |||
'CLI_NO_READLINE' => 1, // Not all distros build smbclient with readline, disable it to get consistent behaviour | |||
'LC_ALL' => Server::LOCALE, | |||
'LANG' => Server::LOCALE, | |||
'COLUMNS' => 8192 // prevent smbclient from line-wrapping it's output | |||
@@ -91,7 +92,7 @@ class RawConnection { | |||
public function isValid(): bool { | |||
if (is_resource($this->process)) { | |||
$status = proc_get_status($this->process); | |||
return (bool)$status['running']; | |||
return $status['running']; | |||
} else { | |||
return false; | |||
} | |||
@@ -109,13 +110,30 @@ class RawConnection { | |||
return $result; | |||
} | |||
/** | |||
* read output till the next prompt | |||
* | |||
* @return string|false | |||
*/ | |||
public function readTillPrompt() { | |||
$output = ""; | |||
do { | |||
$chunk = $this->readLine('\> '); | |||
if ($chunk === false) { | |||
return false; | |||
} | |||
$output .= $chunk; | |||
} while (strlen($chunk) == 4096 && strpos($chunk, "smb:") === false); | |||
return $output; | |||
} | |||
/** | |||
* read a line of output | |||
* | |||
* @return string|false | |||
*/ | |||
public function readLine() { | |||
return stream_get_line($this->getOutputStream(), 4086, "\n"); | |||
public function readLine(string $end = "\n") { | |||
return stream_get_line($this->getOutputStream(), 4096, $end); | |||
} | |||
/** | |||
@@ -202,6 +220,14 @@ class RawConnection { | |||
* @psalm-assert null $this->process | |||
*/ | |||
public function close(bool $terminate = true): void { | |||
$this->close_process($terminate); | |||
} | |||
/** | |||
* @param bool $terminate | |||
* @psalm-assert null $this->process | |||
*/ | |||
protected function close_process(bool $terminate = true): void { | |||
if (!is_resource($this->process)) { | |||
return; | |||
} |
@@ -345,11 +345,17 @@ class Share extends AbstractShare { | |||
// since returned stream is closed by the caller we need to create a new instance | |||
// since we can't re-use the same file descriptor over multiple calls | |||
$connection = $this->getConnection(); | |||
stream_set_blocking($connection->getOutputStream(), false); | |||
$connection->write('get ' . $source . ' ' . $this->system->getFD(5)); | |||
$connection->write('exit'); | |||
$fh = $connection->getFileOutputStream(); | |||
stream_context_set_option($fh, 'file', 'connection', $connection); | |||
$fh = CallbackWrapper::wrap($fh, function() use ($connection) { | |||
$connection->write(''); | |||
}); | |||
if (!is_resource($fh)) { | |||
throw new Exception("Failed to wrap file output"); | |||
} | |||
return $fh; | |||
} | |||
@@ -374,7 +380,9 @@ class Share extends AbstractShare { | |||
// use a close callback to ensure the upload is finished before continuing | |||
// this also serves as a way to keep the connection in scope | |||
$stream = CallbackWrapper::wrap($fh, null, null, function () use ($connection) { | |||
$stream = CallbackWrapper::wrap($fh, function() use ($connection) { | |||
$connection->write(''); | |||
}, null, function () use ($connection) { | |||
$connection->close(false); // dont terminate, give the upload some time | |||
}); | |||
if (is_resource($stream)) { | |||
@@ -446,7 +454,7 @@ class Share extends AbstractShare { | |||
* @return string[] | |||
*/ | |||
protected function execute(string $command): array { | |||
$this->connect()->write($command . PHP_EOL); | |||
$this->connect()->write($command); | |||
return $this->connect()->read(); | |||
} | |||