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
33namespace 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 */
42class 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|\phpseclib\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 (empty($remote_file)) {
148            user_error('remote_file cannot be blank', E_USER_NOTICE);
149            return false;
150        }
151
152        if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to
153            return false;
154        }
155
156        $temp = $this->_receive();
157        if ($temp !== chr(0)) {
158            return false;
159        }
160
161        if ($this->mode == self::MODE_SSH2) {
162            $this->packet_size = $this->ssh->packet_size_client_to_server[SSH2::CHANNEL_EXEC] - 4;
163        }
164
165        $remote_file = basename($remote_file);
166
167        if ($mode == self::SOURCE_STRING) {
168            $size = strlen($data);
169        } else {
170            if (!is_file($data)) {
171                user_error("$data is not a valid file", E_USER_NOTICE);
172                return false;
173            }
174
175            $fp = @fopen($data, 'rb');
176            if (!$fp) {
177                return false;
178            }
179            $size = filesize($data);
180        }
181
182        $this->_send('C0644 ' . $size . ' ' . $remote_file . "\n");
183
184        $temp = $this->_receive();
185        if ($temp !== chr(0)) {
186            return false;
187        }
188
189        $sent = 0;
190        while ($sent < $size) {
191            $temp = $mode & self::SOURCE_STRING ? substr($data, $sent, $this->packet_size) : fread($fp, $this->packet_size);
192            $this->_send($temp);
193            $sent+= strlen($temp);
194
195            if (is_callable($callback)) {
196                call_user_func($callback, $sent);
197            }
198        }
199        $this->_close();
200
201        if ($mode != self::SOURCE_STRING) {
202            fclose($fp);
203        }
204
205        return true;
206    }
207
208    /**
209     * Downloads a file from the SCP server.
210     *
211     * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
212     * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
213     * operation
214     *
215     * @param string $remote_file
216     * @param string $local_file
217     * @return mixed
218     * @access public
219     */
220    function get($remote_file, $local_file = false)
221    {
222        if (!isset($this->ssh)) {
223            return false;
224        }
225
226        if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { // -f = from
227            return false;
228        }
229
230        $this->_send("\0");
231
232        if (!preg_match('#(?<perms>[^ ]+) (?<size>\d+) (?<name>.+)#', rtrim($this->_receive()), $info)) {
233            return false;
234        }
235
236        $this->_send("\0");
237
238        $size = 0;
239
240        if ($local_file !== false) {
241            $fp = @fopen($local_file, 'wb');
242            if (!$fp) {
243                return false;
244            }
245        }
246
247        $content = '';
248        while ($size < $info['size']) {
249            $data = $this->_receive();
250            // SCP usually seems to split stuff out into 16k chunks
251            $size+= strlen($data);
252
253            if ($local_file === false) {
254                $content.= $data;
255            } else {
256                fputs($fp, $data);
257            }
258        }
259
260        $this->_close();
261
262        if ($local_file !== false) {
263            fclose($fp);
264            return true;
265        }
266
267        return $content;
268    }
269
270    /**
271     * Sends a packet to an SSH server
272     *
273     * @param string $data
274     * @access private
275     */
276    function _send($data)
277    {
278        switch ($this->mode) {
279            case self::MODE_SSH2:
280                $this->ssh->_send_channel_packet(SSH2::CHANNEL_EXEC, $data);
281                break;
282            case self::MODE_SSH1:
283                $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($data), $data);
284                $this->ssh->_send_binary_packet($data);
285        }
286    }
287
288    /**
289     * Receives a packet from an SSH server
290     *
291     * @return string
292     * @access private
293     */
294    function _receive()
295    {
296        switch ($this->mode) {
297            case self::MODE_SSH2:
298                return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true);
299            case self::MODE_SSH1:
300                if (!$this->ssh->bitmap) {
301                    return false;
302                }
303                while (true) {
304                    $response = $this->ssh->_get_binary_packet();
305                    switch ($response[SSH1::RESPONSE_TYPE]) {
306                        case NET_SSH1_SMSG_STDOUT_DATA:
307                            if (strlen($response[SSH1::RESPONSE_DATA]) < 4) {
308                                return false;
309                            }
310                            extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA]));
311                            return $this->ssh->_string_shift($response[SSH1::RESPONSE_DATA], $length);
312                        case NET_SSH1_SMSG_STDERR_DATA:
313                            break;
314                        case NET_SSH1_SMSG_EXITSTATUS:
315                            $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION));
316                            fclose($this->ssh->fsock);
317                            $this->ssh->bitmap = 0;
318                            return false;
319                        default:
320                            user_error('Unknown packet received', E_USER_NOTICE);
321                            return false;
322                    }
323                }
324        }
325    }
326
327    /**
328     * Closes the connection to an SSH server
329     *
330     * @access private
331     */
332    function _close()
333    {
334        switch ($this->mode) {
335            case self::MODE_SSH2:
336                $this->ssh->_close_channel(SSH2::CHANNEL_EXEC, true);
337                break;
338            case self::MODE_SSH1:
339                $this->ssh->disconnect();
340        }
341    }
342}
343