2
###################################################################
4
# This class implements a SMB stream wrapper based on 'smbclient'
6
# Date: lun oct 22 10:35:35 CEST 2007
8
# Homepage: http://www.phpclasses.org/smb4php
10
# Copyright (c) 2007 Victor M. Varela <vmvarela@gmail.com>
12
# This program is free software; you can redistribute it and/or
13
# modify it under the terms of the GNU General Public License
14
# as published by the Free Software Foundation; either version 2
15
# of the License, or (at your option) any later version.
17
# This program is distributed in the hope that it will be useful,
18
# but WITHOUT ANY WARRANTY; without even the implied warranty of
19
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
# GNU General Public License for more details.
22
# Addition 17/12/2012 Frank Karlitschek (frank@owncloud.org)
23
# On the official website http://www.phpclasses.org/smb4php the
24
# license is listed as LGPL so we assume that this is
25
# dual-licensed GPL/LGPL
26
###################################################################
28
define ('SMB4PHP_VERSION', '0.8');
30
###################################################################
31
# CONFIGURATION SECTION - Change for your needs
32
###################################################################
34
define ('SMB4PHP_SMBCLIENT', 'smbclient');
35
define ('SMB4PHP_SMBOPTIONS', 'TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192');
36
define ('SMB4PHP_AUTHMODE', 'arg'); # set to 'env' to use USER enviroment variable
38
###################################################################
39
# SMB - commands that does not need an instance
40
###################################################################
42
$GLOBALS['__smb_cache'] = array ('stat' => array (), 'dir' => array ());
46
function parse_url ($url) {
47
$pu = parse_url (trim($url));
48
foreach (array ('domain', 'user', 'pass', 'host', 'port', 'path') as $i) {
49
if (! isset($pu[$i])) {
53
if (count ($userdomain = explode (';', urldecode ($pu['user']))) > 1) {
54
@list ($pu['domain'], $pu['user']) = $userdomain;
56
$path = preg_replace (array ('/^\//', '/\/$/'), '', urldecode ($pu['path']));
57
list ($pu['share'], $pu['path']) = (preg_match ('/^([^\/]+)\/(.*)/', $path, $regs))
58
? array ($regs[1], preg_replace ('/\//', '\\', $regs[2]))
60
$pu['type'] = $pu['path'] ? 'path' : ($pu['share'] ? 'share' : ($pu['host'] ? 'host' : '**error**'));
61
if (! ($pu['port'] = intval(@$pu['port']))) {
65
// decode user and password
66
$pu['user'] = urldecode($pu['user']);
67
$pu['pass'] = urldecode($pu['pass']);
72
function look ($purl) {
73
return smb::client ('-L ' . escapeshellarg ($purl['host']), $purl);
77
function execute ($command, $purl) {
78
return smb::client ('-d 0 '
79
. escapeshellarg ('//' . $purl['host'] . '/' . $purl['share'])
80
. ' -c ' . escapeshellarg ($command), $purl
84
function client ($params, $purl) {
86
static $regexp = array (
87
'^added interface ip=(.*) bcast=(.*) nmask=(.*)$' => 'skip',
88
'Anonymous login successful' => 'skip',
89
'^Domain=\[(.*)\] OS=\[(.*)\] Server=\[(.*)\]$' => 'skip',
90
'^\tSharename[ ]+Type[ ]+Comment$' => 'shares',
91
'^\t---------[ ]+----[ ]+-------$' => 'skip',
92
'^\tServer [ ]+Comment$' => 'servers',
93
'^\t---------[ ]+-------$' => 'skip',
94
'^\tWorkgroup[ ]+Master$' => 'workg',
95
'^\t(.*)[ ]+(Disk|IPC)[ ]+IPC.*$' => 'skip',
96
'^\tIPC\\\$(.*)[ ]+IPC' => 'skip',
97
'^\t(.*)[ ]+(Disk)[ ]+(.*)$' => 'share',
98
'^\t(.*)[ ]+(Printer)[ ]+(.*)$' => 'skip',
99
'([0-9]+) blocks of size ([0-9]+)\. ([0-9]+) blocks available' => 'skip',
100
'Got a positive name query response from ' => 'skip',
101
'^(session setup failed): (.*)$' => 'error',
102
'^(.*): ERRSRV - ERRbadpw' => 'error',
103
'^Error returning browse list: (.*)$' => 'error',
104
'^tree connect failed: (.*)$' => 'error',
105
'^(Connection to .* failed)(.*)$' => 'error-connect',
106
'^NT_STATUS_(.*) ' => 'error',
107
'^NT_STATUS_(.*)\$' => 'error',
108
'ERRDOS - ERRbadpath \((.*).\)' => 'error',
109
'cd (.*): (.*)$' => 'error',
110
'^cd (.*): NT_STATUS_(.*)' => 'error',
111
'^\t(.*)$' => 'srvorwg',
112
'^([0-9]+)[ ]+([0-9]+)[ ]+(.*)$' => 'skip',
113
'^Job ([0-9]+) cancelled' => 'skip',
114
'^[ ]+(.*)[ ]+([0-9]+)[ ]+(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[ ](Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[ ]+([0-9]+)[ ]+([0-9]{2}:[0-9]{2}:[0-9]{2})[ ]([0-9]{4})$' => 'files',
115
'^message start: ERRSRV - (ERRmsgoff)' => 'error'
118
if (SMB4PHP_AUTHMODE == 'env') {
119
putenv("USER={$purl['user']}%{$purl['pass']}");
122
$auth = ($purl['user'] <> '' ? (' -U ' . escapeshellarg ($purl['user'] . '%' . $purl['pass'])) : '');
124
if ($purl['domain'] <> '') {
125
$auth .= ' -W ' . escapeshellarg ($purl['domain']);
127
$port = ($purl['port'] <> 139 ? ' -p ' . escapeshellarg ($purl['port']) : '');
128
$options = '-O ' . escapeshellarg(SMB4PHP_SMBOPTIONS);
130
// this put env is necessary to read the output of smbclient correctly
131
$old_locale = getenv('LC_ALL');
132
putenv('LC_ALL=en_US.UTF-8');
133
$output = popen ('TZ=UTC '.SMB4PHP_SMBCLIENT." -N {$auth} {$options} {$port} {$options} {$params} 2>/dev/null", 'r');
136
$info['info']= array ();
138
while ($line = fgets ($output, 4096)) {
139
list ($tag, $regs, $i) = array ('skip', array (), array ());
141
foreach ($regexp as $r => $t) if (preg_match ('/'.$r.'/', $line, $regs)) {
146
case 'skip': continue;
147
case 'shares': $mode = 'shares'; break;
148
case 'servers': $mode = 'servers'; break;
149
case 'workg': $mode = 'workgroups'; break;
151
list($name, $type) = array (
152
trim(substr($line, 1, 15)),
153
trim(strtolower(substr($line, 17, 10)))
155
$i = ($type <> 'disk' && preg_match('/^(.*) Disk/', $line, $regs))
156
? array(trim($regs[1]), 'disk')
157
: array($name, 'disk');
160
list ($name, $master) = array (
161
strtolower(trim(substr($line,1,21))),
162
strtolower(trim(substr($line, 22)))
164
$i = ($mode == 'servers') ? array ($name, "server") : array ($name, "workgroup", $master);
167
list ($attr, $name) = preg_match ("/^(.*)[ ]+([D|A|H|S|R]+)$/", trim ($regs[1]), $regs2)
168
? array (trim ($regs2[2]), trim ($regs2[1]))
169
: array ('', trim ($regs[1]));
170
list ($his, $im) = array (
171
explode(':', $regs[6]), 1 + strpos("JanFebMarAprMayJunJulAugSepOctNovDec", $regs[4]) / 3);
172
$i = ($name <> '.' && $name <> '..')
175
(strpos($attr,'D') === FALSE) ? 'file' : 'folder',
177
'size' => intval($regs[2]),
178
'time' => mktime ($his[0], $his[1], $his[2], $im, $regs[5], $regs[7])
183
if(substr($regs[0],0,22)=='NT_STATUS_NO_SUCH_FILE'){
185
}elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_NAME_COLLISION'){
187
}elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_PATH_NOT_FOUND'){
189
}elseif(substr($regs[0],0,31)=='NT_STATUS_OBJECT_NAME_NOT_FOUND'){
191
}elseif(substr($regs[0],0,29)=='NT_STATUS_FILE_IS_A_DIRECTORY'){
194
trigger_error($regs[0].' params('.$params.')', E_USER_ERROR);
195
case 'error-connect':
196
// connection error can happen after obtaining share list if
197
// NetBIOS is disabled/blocked on the target server,
198
// in which case we keep the info and continue
203
if ($i) switch ($i[1]) {
205
case 'folder': $info['info'][$i[0]] = $i;
208
case 'workgroup': $info[$i[1]][] = $i[0];
215
// restore previous locale
216
if ($old_locale===false) {
219
putenv('LC_ALL='.$old_locale);
228
function url_stat ($url, $flags = STREAM_URL_STAT_LINK) {
229
if ($s = smb::getstatcache($url)) {
232
list ($stat, $pu) = array (false, smb::parse_url ($url));
233
switch ($pu['type']) {
235
if ($o = smb::look ($pu))
236
$stat = stat ("/tmp");
238
trigger_error ("url_stat(): list failed for host '{$pu['host']}'", E_USER_WARNING);
241
if ($o = smb::look ($pu)) {
243
$lshare = strtolower ($pu['share']); # fix by Eric Leung
244
foreach ($o['disk'] as $s) if ($lshare == strtolower($s)) {
246
$stat = stat ("/tmp");
250
trigger_error ("url_stat(): disk resource '{$lshare}' not found in '{$pu['host']}'", E_USER_WARNING);
254
if ($o = smb::execute ('dir "'.$pu['path'].'"', $pu)) {
255
$p = explode('\\', $pu['path']);
256
$name = $p[count($p)-1];
257
if (isset ($o['info'][$name])) {
258
$stat = smb::addstatcache ($url, $o['info'][$name]);
260
trigger_error ("url_stat(): path '{$pu['path']}' not found", E_USER_WARNING);
264
// trigger_error ("url_stat(): dir failed for path '{$pu['path']}'", E_USER_WARNING);
267
default: trigger_error ('error in URL', E_USER_ERROR);
272
function addstatcache ($url, $info) {
273
$url = str_replace('//', '/', $url);
274
$url = rtrim($url, '/');
276
$is_file = (strpos ($info['attr'],'D') === FALSE);
277
$s = ($is_file) ? stat ('/etc/passwd') : stat ('/tmp');
278
$s[7] = $s['size'] = $info['size'];
279
$s[8] = $s[9] = $s[10] = $s['atime'] = $s['mtime'] = $s['ctime'] = $info['time'];
280
return $__smb_cache['stat'][$url] = $s;
283
function getstatcache ($url) {
284
$url = str_replace('//', '/', $url);
285
$url = rtrim($url, '/');
287
return isset ($__smb_cache['stat'][$url]) ? $__smb_cache['stat'][$url] : FALSE;
290
function clearstatcache ($url='') {
291
$url = str_replace('//', '/', $url);
292
$url = rtrim($url, '/');
294
if ($url == '') $__smb_cache['stat'] = array (); else unset ($__smb_cache['stat'][$url]);
300
function unlink ($url) {
301
$pu = smb::parse_url($url);
302
if ($pu['type'] <> 'path') trigger_error('unlink(): error in URL', E_USER_ERROR);
303
smb::clearstatcache ($url);
304
smb_stream_wrapper::cleardircache (dirname($url));
305
return smb::execute ('del "'.$pu['path'].'"', $pu);
308
function rename ($url_from, $url_to) {
310
list ($from, $to) = array (smb::parse_url($url_from), smb::parse_url($url_to));
311
if ($from['host'] <> $to['host'] ||
312
$from['share'] <> $to['share'] ||
313
$from['user'] <> $to['user'] ||
314
$from['pass'] <> $to['pass'] ||
315
$from['domain'] <> $to['domain']) {
316
trigger_error('rename(): FROM & TO must be in same server-share-user-pass-domain', E_USER_ERROR);
318
if ($from['type'] <> 'path' || $to['type'] <> 'path') {
319
trigger_error('rename(): error in URL', E_USER_ERROR);
321
smb::clearstatcache ($url_from);
323
// check if target file exists
324
if (smb::url_stat($url_to)) {
325
// delete target file first
326
$cmd = 'del "' . $to['path'] . '"; ';
329
$cmd .= 'rename "' . $from['path'] . '" "' . $to['path'] . '"';
330
$result = smb::execute($cmd, $to);
332
// clear again, else the cache will return the info
334
smb::clearstatcache ($url_to);
336
return $result !== false;
339
function mkdir ($url, $mode, $options) {
340
$pu = smb::parse_url($url);
341
if ($pu['type'] <> 'path') trigger_error('mkdir(): error in URL', E_USER_ERROR);
342
return smb::execute ('mkdir "'.$pu['path'].'"', $pu)!==false;
345
function rmdir ($url) {
346
$pu = smb::parse_url($url);
347
if ($pu['type'] <> 'path') trigger_error('rmdir(): error in URL', E_USER_ERROR);
348
smb::clearstatcache ($url);
349
smb_stream_wrapper::cleardircache (dirname($url));
350
return smb::execute ('rmdir "'.$pu['path'].'"', $pu)!==false;
355
###################################################################
356
# SMB_STREAM_WRAPPER - class to be registered for smb:// URLs
357
###################################################################
359
class smb_stream_wrapper extends smb {
363
private $stream, $url, $parsed_url = array (), $mode, $tmpfile;
364
private $need_flush = FALSE;
365
private $dir = array (), $dir_index = -1;
370
function dir_opendir ($url, $options) {
371
if ($d = $this->getdircache ($url)) {
373
$this->dir_index = 0;
376
$pu = smb::parse_url ($url);
377
switch ($pu['type']) {
379
if ($o = smb::look ($pu)) {
380
$this->dir = $o['disk'];
381
$this->dir_index = 0;
383
trigger_error ("dir_opendir(): list failed for host '{$pu['host']}'", E_USER_WARNING);
389
if (is_array($o = smb::execute ('dir "'.$pu['path'].'\*"', $pu))) {
390
$this->dir = array_keys($o['info']);
391
$this->dir_index = 0;
392
$this->adddircache ($url, $this->dir);
393
if(substr($url,-1,1)=='/'){
394
$url=substr($url,0,-1);
396
foreach ($o['info'] as $name => $info) {
397
smb::addstatcache($url . '/' . $name, $info);
400
trigger_error ("dir_opendir(): dir failed for path '".$pu['path']."'", E_USER_WARNING);
405
trigger_error ('dir_opendir(): error in URL', E_USER_ERROR);
411
function dir_readdir () {
412
return ($this->dir_index < count($this->dir)) ? $this->dir[$this->dir_index++] : FALSE;
415
function dir_rewinddir () { $this->dir_index = 0; }
417
function dir_closedir () { $this->dir = array(); $this->dir_index = -1; return TRUE; }
422
function adddircache ($url, $content) {
423
$url = str_replace('//', '/', $url);
424
$url = rtrim($url, '/');
426
return $__smb_cache['dir'][$url] = $content;
429
function getdircache ($url) {
430
$url = str_replace('//', '/', $url);
431
$url = rtrim($url, '/');
433
return isset ($__smb_cache['dir'][$url]) ? $__smb_cache['dir'][$url] : FALSE;
436
function cleardircache ($url='') {
437
$url = str_replace('//', '/', $url);
438
$url = rtrim($url, '/');
441
$__smb_cache['dir'] = array ();
443
unset ($__smb_cache['dir'][$url]);
450
function stream_open ($url, $mode, $options, $opened_path) {
453
$this->parsed_url = $pu = smb::parse_url($url);
454
if ($pu['type'] <> 'path') trigger_error('stream_open(): error in URL', E_USER_ERROR);
460
case 'a+': $this->tmpfile = tempnam('/tmp', 'smb.down.');
461
$result = smb::execute ('get "'.$pu['path'].'" "'.$this->tmpfile.'"', $pu);
462
if($result === false){
470
case 'x+': $this->cleardircache();
471
$this->tmpfile = tempnam('/tmp', 'smb.up.');
472
$this->need_flush=true;
474
$this->stream = fopen ($this->tmpfile, $mode);
478
function stream_close () { return fclose($this->stream); }
480
function stream_read ($count) { return fread($this->stream, $count); }
482
function stream_write ($data) { $this->need_flush = TRUE; return fwrite($this->stream, $data); }
484
function stream_eof () { return feof($this->stream); }
486
function stream_tell () { return ftell($this->stream); }
488
// PATCH: the wrapper must return true when fseek succeeded by returning 0.
489
function stream_seek ($offset, $whence=null) { return fseek($this->stream, $offset, $whence) === 0; }
491
function stream_flush () {
492
if ($this->mode <> 'r' && $this->need_flush) {
493
smb::clearstatcache ($this->url);
494
smb::execute ('put "'.$this->tmpfile.'" "'.$this->parsed_url['path'].'"', $this->parsed_url);
495
$this->need_flush = FALSE;
499
function stream_stat () { return smb::url_stat ($this->url); }
501
function __destruct () {
502
if ($this->tmpfile <> '') {
503
if ($this->need_flush) $this->stream_flush ();
504
unlink ($this->tmpfile);
511
###################################################################
512
# Register 'smb' protocol !
513
###################################################################
515
stream_wrapper_register('smb', 'smb_stream_wrapper')
516
or die ('Failed to register protocol');