Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
<?php
2
 
3
/**
4
 * Pure-PHP implementation of SCP.
5
 *
6
 * PHP version 5
7
 *
8
 * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
9
 *
10
 * Here's a short example of how to use this library:
11
 * <code>
12
 * <?php
13
 *    include 'vendor/autoload.php';
14
 *
15
 *    $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
16
 *    if (!$ssh->login('username', 'password')) {
17
 *        exit('bad login');
18
 *    }
19
 *    $scp = new \phpseclib\Net\SCP($ssh);
20
 *
21
 *    $scp->put('abcd', str_repeat('x', 1024*1024));
22
 * ?>
23
 * </code>
24
 *
25
 * @category  Net
26
 * @package   SCP
27
 * @author    Jim Wigginton <terrafrost@php.net>
28
 * @copyright 2010 Jim Wigginton
29
 * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
30
 * @link      http://phpseclib.sourceforge.net
31
 */
32
 
33
namespace phpseclib\Net;
34
 
35
/**
36
 * Pure-PHP implementations of SCP.
37
 *
38
 * @package SCP
39
 * @author  Jim Wigginton <terrafrost@php.net>
40
 * @access  public
41
 */
42
class SCP
43
{
44
    /**#@+
45
     * @access public
46
     * @see \phpseclib\Net\SCP::put()
47
     */
48
    /**
49
     * Reads data from a local file.
50
     */
51
    const SOURCE_LOCAL_FILE = 1;
52
    /**
53
     * Reads data from a string.
54
     */
55
    const SOURCE_STRING = 2;
56
    /**#@-*/
57
 
58
    /**#@+
59
     * @access private
60
     * @see \phpseclib\Net\SCP::_send()
61
     * @see \phpseclib\Net\SCP::_receive()
62
    */
63
    /**
64
     * SSH1 is being used.
65
     */
66
    const MODE_SSH1 = 1;
67
    /**
68
     * SSH2 is being used.
69
     */
70
    const MODE_SSH2 =  2;
71
    /**#@-*/
72
 
73
    /**
74
     * SSH Object
75
     *
76
     * @var object
77
     * @access private
78
     */
79
    var $ssh;
80
 
81
    /**
82
     * Packet Size
83
     *
84
     * @var int
85
     * @access private
86
     */
87
    var $packet_size;
88
 
89
    /**
90
     * Mode
91
     *
92
     * @var int
93
     * @access private
94
     */
95
    var $mode;
96
 
97
    /**
98
     * Default Constructor.
99
     *
100
     * Connects to an SSH server
101
     *
102
     * @param \phpseclib\Net\SSH1|\phpseclin\Net\SSH2 $ssh
103
     * @return \phpseclib\Net\SCP
104
     * @access public
105
     */
106
    function __construct($ssh)
107
    {
108
        if ($ssh instanceof SSH2) {
109
            $this->mode = self::MODE_SSH2;
110
        } elseif ($ssh instanceof SSH1) {
111
            $this->packet_size = 50000;
112
            $this->mode = self::MODE_SSH1;
113
        } else {
114
            return;
115
        }
116
 
117
        $this->ssh = $ssh;
118
    }
119
 
120
    /**
121
     * Uploads a file to the SCP server.
122
     *
123
     * By default, \phpseclib\Net\SCP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.
124
     * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SCP::get(), you will get a file, twelve bytes
125
     * long, containing 'filename.ext' as its contents.
126
     *
127
     * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior.  With self::SOURCE_LOCAL_FILE, $remote_file will
128
     * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how
129
     * large $remote_file will be, as well.
130
     *
131
     * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take
132
     * care of that, yourself.
133
     *
134
     * @param string $remote_file
135
     * @param string $data
136
     * @param int $mode
137
     * @param callable $callback
138
     * @return bool
139
     * @access public
140
     */
141
    function put($remote_file, $data, $mode = self::SOURCE_STRING, $callback = null)
142
    {
143
        if (!isset($this->ssh)) {
144
            return false;
145
        }
146
 
147
        if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to
148
            return false;
149
        }
150
 
151
        $temp = $this->_receive();
152
        if ($temp !== chr(0)) {
153
            return false;
154
        }
155
 
156
        if ($this->mode == self::MODE_SSH2) {
157
            $this->packet_size = $this->ssh->packet_size_client_to_server[SSH2::CHANNEL_EXEC] - 4;
158
        }
159
 
160
        $remote_file = basename($remote_file);
161
 
162
        if ($mode == self::SOURCE_STRING) {
163
            $size = strlen($data);
164
        } else {
165
            if (!is_file($data)) {
166
                user_error("$data is not a valid file", E_USER_NOTICE);
167
                return false;
168
            }
169
 
170
            $fp = @fopen($data, 'rb');
171
            if (!$fp) {
172
                return false;
173
            }
174
            $size = filesize($data);
175
        }
176
 
177
        $this->_send('C0644 ' . $size . ' ' . $remote_file . "\n");
178
 
179
        $temp = $this->_receive();
180
        if ($temp !== chr(0)) {
181
            return false;
182
        }
183
 
184
        $sent = 0;
185
        while ($sent < $size) {
186
            $temp = $mode & self::SOURCE_STRING ? substr($data, $sent, $this->packet_size) : fread($fp, $this->packet_size);
187
            $this->_send($temp);
188
            $sent+= strlen($temp);
189
 
190
            if (is_callable($callback)) {
191
                call_user_func($callback, $sent);
192
            }
193
        }
194
        $this->_close();
195
 
196
        if ($mode != self::SOURCE_STRING) {
197
            fclose($fp);
198
        }
199
 
200
        return true;
201
    }
202
 
203
    /**
204
     * Downloads a file from the SCP server.
205
     *
206
     * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
207
     * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
208
     * operation
209
     *
210
     * @param string $remote_file
211
     * @param string $local_file
212
     * @return mixed
213
     * @access public
214
     */
215
    function get($remote_file, $local_file = false)
216
    {
217
        if (!isset($this->ssh)) {
218
            return false;
219
        }
220
 
221
        if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { // -f = from
222
            return false;
223
        }
224
 
225
        $this->_send("\0");
226
 
227
        if (!preg_match('#(?<perms>[^ ]+) (?<size>\d+) (?<name>.+)#', rtrim($this->_receive()), $info)) {
228
            return false;
229
        }
230
 
231
        $this->_send("\0");
232
 
233
        $size = 0;
234
 
235
        if ($local_file !== false) {
236
            $fp = @fopen($local_file, 'wb');
237
            if (!$fp) {
238
                return false;
239
            }
240
        }
241
 
242
        $content = '';
243
        while ($size < $info['size']) {
244
            $data = $this->_receive();
245
            // SCP usually seems to split stuff out into 16k chunks
246
            $size+= strlen($data);
247
 
248
            if ($local_file === false) {
249
                $content.= $data;
250
            } else {
251
                fputs($fp, $data);
252
            }
253
        }
254
 
255
        $this->_close();
256
 
257
        if ($local_file !== false) {
258
            fclose($fp);
259
            return true;
260
        }
261
 
262
        return $content;
263
    }
264
 
265
    /**
266
     * Sends a packet to an SSH server
267
     *
268
     * @param string $data
269
     * @access private
270
     */
271
    function _send($data)
272
    {
273
        switch ($this->mode) {
274
            case self::MODE_SSH2:
275
                $this->ssh->_send_channel_packet(SSH2::CHANNEL_EXEC, $data);
276
                break;
277
            case self::MODE_SSH1:
278
                $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($data), $data);
279
                $this->ssh->_send_binary_packet($data);
280
        }
281
    }
282
 
283
    /**
284
     * Receives a packet from an SSH server
285
     *
286
     * @return string
287
     * @access private
288
     */
289
    function _receive()
290
    {
291
        switch ($this->mode) {
292
            case self::MODE_SSH2:
293
                return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true);
294
            case self::MODE_SSH1:
295
                if (!$this->ssh->bitmap) {
296
                    return false;
297
                }
298
                while (true) {
299
                    $response = $this->ssh->_get_binary_packet();
300
                    switch ($response[SSH1::RESPONSE_TYPE]) {
301
                        case NET_SSH1_SMSG_STDOUT_DATA:
302
                            extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA]));
303
                            return $this->ssh->_string_shift($response[SSH1::RESPONSE_DATA], $length);
304
                        case NET_SSH1_SMSG_STDERR_DATA:
305
                            break;
306
                        case NET_SSH1_SMSG_EXITSTATUS:
307
                            $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION));
308
                            fclose($this->ssh->fsock);
309
                            $this->ssh->bitmap = 0;
310
                            return false;
311
                        default:
312
                            user_error('Unknown packet received', E_USER_NOTICE);
313
                            return false;
314
                    }
315
                }
316
        }
317
    }
318
 
319
    /**
320
     * Closes the connection to an SSH server
321
     *
322
     * @access private
323
     */
324
    function _close()
325
    {
326
        switch ($this->mode) {
327
            case self::MODE_SSH2:
328
                $this->ssh->_close_channel(SSH2::CHANNEL_EXEC, true);
329
                break;
330
            case self::MODE_SSH1:
331
                $this->ssh->disconnect();
332
        }
333
    }
334
}