]> source.dussan.org Git - nextcloud-server.git/commitdiff
update icewind/smb to 3.5.1
authorRobin Appelman <robin@icewind.nl>
Thu, 4 Nov 2021 14:05:37 +0000 (15:05 +0100)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Thu, 20 Jan 2022 19:14:10 +0000 (19:14 +0000)
Signed-off-by: Robin Appelman <robin@icewind.nl>
20 files changed:
apps/files_external/3rdparty/.gitignore
apps/files_external/3rdparty/composer.json
apps/files_external/3rdparty/composer.lock
apps/files_external/3rdparty/composer/ClassLoader.php
apps/files_external/3rdparty/composer/InstalledVersions.php
apps/files_external/3rdparty/composer/autoload_classmap.php
apps/files_external/3rdparty/composer/autoload_static.php
apps/files_external/3rdparty/composer/installed.json
apps/files_external/3rdparty/composer/installed.php
apps/files_external/3rdparty/icewind/smb/README.md
apps/files_external/3rdparty/icewind/smb/src/IShare.php
apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php [new file with mode: 0644]
apps/files_external/3rdparty/icewind/smb/src/Native/NativeFileInfo.php
apps/files_external/3rdparty/icewind/smb/src/Native/NativeShare.php
apps/files_external/3rdparty/icewind/smb/src/Native/NativeState.php
apps/files_external/3rdparty/icewind/smb/src/System.php
apps/files_external/3rdparty/icewind/smb/src/Wrapped/Connection.php
apps/files_external/3rdparty/icewind/smb/src/Wrapped/NotifyHandler.php
apps/files_external/3rdparty/icewind/smb/src/Wrapped/RawConnection.php
apps/files_external/3rdparty/icewind/smb/src/Wrapped/Share.php

index 651eb60572de852090f0fa3bdbea35081f382da4..e787d39fca68c62c9309de51d1ab175541ac7a11 100644 (file)
@@ -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*
index d8854aa976a5cdbdbe855744db7f2957c14f1dac..21ae38a99964285c934cac3c30b71b298dd96b5f 100644 (file)
@@ -9,6 +9,6 @@
        },
        "require": {
                "icewind/streams": "0.7.4",
-               "icewind/smb": "3.4.1"
+               "icewind/smb": "3.5.1"
        }
 }
index 05de684a017abbac0fe3c266ad403c73d35bbf2e..6235bf98e5c735f60aa4be1e193d9fc81a5c9a1d 100644 (file)
@@ -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",
index 6d0c3f2d001d840111263ce405263c8782737ef5..0cd6055d1b7943ea1a8c5f45630517b4f2d56b59 100644 (file)
@@ -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)
 {
index b3a4e1611e6e5104c1d7a48947807a69ffddb9e1..d50e0c9fcc47df4f65268ae1a0b4074990160486 100644 (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()
     {
index d0f82994f2901e9805bab65eae2133264e95fcae..17b94af0e9979d29122336d847802fdacf2f4cfe 100644 (file)
@@ -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',
index 899982f2a678ea7b8c150605cf65763102943fb4..1d309dcd6f1ca4e7d0fe00f96ec685e1b33819b5 100644 (file)
@@ -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',
index 7405962c70cf11d046859101129bb04ed28c0a7e..c2e3ffb0e4b77e163169c8f2290915512c7de158 100644 (file)
@@ -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"
         },
index 7dfd1c420f7e8375bf6c1de6fd372640809a254e..2b4e3329b365aeba1f0bd02a51bf9f72d4959aee 100644 (file)
@@ -5,7 +5,7 @@
         'type' => 'library',
         'install_path' => __DIR__ . '/../',
         'aliases' => array(),
-        'reference' => '70483a16a3a232758979bb6fa363629b5a16b6a4',
+        'reference' => 'cd72330b8f669e3dc81388be5a92171404f36fec',
         'name' => 'files_external/3rdparty',
         'dev' => true,
     ),
             '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(
index 272c4ebedcdb8bf52bb7be08f806bd8d5a5be59f..fec1faefbadb68ff911cc1ace8f5b37d77cac39a 100644 (file)
@@ -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 ###
 
index 6ac6e0d2d159e9226cb0e71ca94810f70e7d4b61..40213b93a99638afa1e8a265e6b9504b332a8de3 100644 (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
diff --git a/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php b/apps/files_external/3rdparty/icewind/smb/src/KerberosApacheAuth.php
new file mode 100644 (file)
index 0000000..03551aa
--- /dev/null
@@ -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);
+               }
+       }
+}
index 539bb728426715d29c773f893ce1c89d3dae8964..85fb0274ac18e6682c402e818381c884b5d507c0 100644 (file)
@@ -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);
                }
index 03ec501b830e36a94c753ef0e32ebf04eb3be1a6..8c4eab2a60f9056538040e189f101984915fffde 100644 (file)
@@ -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);
        }
index e1a13ce3e72f7f0ffaa8e0271088d88eeffaf641..088e6f733d1230264ebb7de5d64bf4153c381e67 100644 (file)
@@ -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());
 
index 919907477abc6bb62135ba9c8bb3ba2bcf600df4..d3475e7a5cb6e869b442bf7b2cfe6aa68cbfce70 100644 (file)
@@ -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];
        }
index 31b72b05d97b2fdc3c855f6c4884f9dd8e4cf76a..cc73ac1ad14c6b21f61bcd0d6dc6a476e6d98232 100644 (file)
@@ -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);
        }
 }
index 18451f4daa69c0dd021df2992a77095550e65543..ecb5bb1e3c1120ea21535743416930bcd5916c1d 100644 (file)
@@ -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;
+                                       }
                                }
-                       });
+                       };
                }
        }
 
index 26a17cc584b8a9243631faeda953cb1c10f1ea66..4aec674c3da0f0c3ecc592a0ed68f201007fbe2a 100644 (file)
@@ -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;
                }
index 68446d380e08d4252d3eb86bd26f168aa4da62c8..eb68d3800b382627261d2624fdf7a483a5d69795 100644 (file)
@@ -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();
        }