Completed
Push — master ( 700a98...214a89 )
by Sebastian
05:41
created

Sftp::createClient()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 25
ccs 0
cts 15
cp 0
rs 8.5806
cc 4
eloc 15
nc 3
nop 0
crap 20
1
<?php
2
namespace phpbu\App\Backup\Sync;
3
4
use phpbu\App\Backup\Collector;
5
use phpbu\App\Backup\Path;
6
use phpbu\App\Backup\Target;
7
use phpbu\App\Configuration;
8
use phpbu\App\Result;
9
use phpbu\App\Util;
10
use phpseclib;
11
12
/**
13
 * Sftp sync
14
 *
15
 * @package    phpbu
16
 * @subpackage Backup
17
 * @author     Sebastian Feldmann <[email protected]>
18
 * @copyright  Sebastian Feldmann <[email protected]>
19
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
20
 * @link       http://phpbu.de/
21
 * @since      Class available since Release 1.0.0
22
 */
23
class Sftp extends Xtp
24
{
25
    /**
26
     * @var phpseclib\Net\SFTP
27
     */
28
    protected $sftp;
29
30
    /**
31
     * Remote path where to put the backup
32
     *
33
     * @var Path
34
     */
35
    protected $remotePath;
36
37
    /**
38
     * @var int
39
     */
40
    protected $time;
41
42
    /**
43
     * @var string
44
     */
45
    protected $privateKey;
46
47
    /**
48
     * (non-PHPDoc)
49
     *
50
     * @see    \phpbu\App\Backup\Sync::setup()
51
     * @param  array $config
52
     * @throws \phpbu\App\Backup\Sync\Exception
53
     * @throws \phpbu\App\Exception
54
     */
55 12
    public function setup(array $config)
56
    {
57
        // make sure either password or private key is configured
58 12
        if (!Util\Arr::isSetAndNotEmptyString($config, 'password')
59 12
         && !Util\Arr::isSetAndNotEmptyString($config, 'private_key')) {
60 4
            throw new Exception('\'password\' or \'private_key\' must be presented');
61
        }
62 8
        parent::setup($config);
63
64 8
        $this->time = time();
65 8
        $privateKey = Util\Arr::getValue($config, 'key', '');
66 8
        if (!empty($privateKey)) {
67
            // get absolute private key path
68 2
            $privateKey = Util\Path::toAbsolutePath($privateKey, Configuration::getWorkingDirectory());
69 2
            if (!file_exists($privateKey)) {
70 1
                throw new Exception("Private key not found at specified path");
71
            }
72
        }
73 7
        $this->privateKey = $privateKey;
74 7
        $this->remotePath = new Path($config['path'], $this->time);
75
76 7
        $this->setUpCleanable($config);
77 7
    }
78
79
    /**
80
     * Check for required loaded libraries or extensions.
81
     *
82
     * @throws \phpbu\App\Backup\Sync\Exception
83
     */
84 8
    protected function checkRequirements()
85
    {
86 8
        if (!class_exists('\\phpseclib\\Net\\SFTP')) {
87
            throw new Exception('phpseclib not installed - use composer to install "phpseclib/phpseclib" version 2.x');
88
        }
89 8
    }
90
91
    /**
92
     * Return implemented (*)TP protocol name.
93
     *
94
     * @return string
95
     */
96 1
    protected function getProtocolName()
97
    {
98 1
        return 'SFTP';
99
    }
100
101
    /**
102
     * (non-PHPDoc)
103
     *
104
     * @see    \phpbu\App\Backup\Sync::sync()
105
     * @param  \phpbu\App\Backup\Target $target
106
     * @param  \phpbu\App\Result        $result
107
     * @throws \phpbu\App\Backup\Sync\Exception
108
     */
109 4
    public function sync(Target $target, Result $result)
110
    {
111 4
        $sftp           = $this->createClient();
112 4
        $remoteFilename = $target->getFilename();
113 4
        $localFile      = $target->getPathname();
114
115 4
        $this->validateRemotePath();
116
117 4
        foreach ($this->getRemoteDirectoryList() as $dir) {
118 4
            if (!$sftp->is_dir($dir)) {
119 2
                $result->debug(sprintf('creating remote dir \'%s\'', $dir));
120 2
                $sftp->mkdir($dir);
121
            }
122 4
            $result->debug(sprintf('change to remote dir \'%s\'', $dir));
123 4
            $sftp->chdir($dir);
124
        }
125
126 4
        $result->debug(sprintf('store file \'%s\' as \'%s\'', $localFile, $remoteFilename));
127 4
        $result->debug(sprintf('last error \'%s\'', $sftp->getLastSFTPError()));
128
129 4
        if (!$sftp->put($remoteFilename, $localFile, phpseclib\Net\SFTP::SOURCE_LOCAL_FILE)) {
130 1
            throw new Exception(sprintf('error uploading file: %s - %s', $localFile, $sftp->getLastSFTPError()));
131
        }
132
133
        // run remote cleanup
134 3
        $this->cleanup($target, $result);
135 3
    }
136
137
    /**
138
     * Create a sftp handle.
139
     *
140
     * @return \phpseclib\Net\SFTP
141
     * @throws \phpbu\App\Backup\Sync\Exception
142
     */
143
    protected function createClient() : phpseclib\Net\SFTP
144
    {
145
        if (!$this->sftp) {
146
            // silence phpseclib errors
147
            $old  = error_reporting(0);
148
            $this->sftp = new phpseclib\Net\SFTP($this->host);
149
            $auth = $this->getAuth();
150
151
            if (!$this->sftp->login($this->user, $auth)) {
152
                error_reporting($old);
153
                throw new Exception(
154
                    sprintf(
155
                        'authentication failed for %s@%s%s',
156
                        $this->user,
157
                        $this->host,
158
                        empty($this->password) ? '' : ' with password ****'
159
                    )
160
                );
161
            }
162
            // restore old error reporting
163
            error_reporting($old);
164
        }
165
166
        return $this->sftp;
167
    }
168
169
    /**
170
     * If a relative path is configured, determine absolute path and update local remote.
171
     *
172
     * @throws \phpbu\App\Backup\Sync\Exception
173
     */
174 4
    protected function validateRemotePath()
175
    {
176 4
        if (!Util\Path::isAbsolutePath($this->remotePath->getPath())) {
177 1
            $sftp             = $this->createClient();
178 1
            $this->remotePath = new Path($sftp->realpath('.') . '/' . $this->remotePath->getPathRaw(), $this->time);
179
        }
180 4
    }
181
182
    /**
183
     * Return a phpseclib authentication thingy.
184
     *
185
     * @return \phpseclib\Crypt\RSA|string
186
     */
187
    private function getAuth()
188
    {
189
        $auth = $this->password;
190
        // if private key should be used
191
        if ($this->privateKey) {
192
            $auth = new phpseclib\Crypt\RSA();
193
            $auth->loadKey(file_get_contents($this->privateKey));
194
            if ($this->password) {
195
                $auth->setPassword($this->password);
196
            }
197
        }
198
        return $auth;
199
    }
200
201
    /**
202
     * Return list of remote directories to travers.
203
     *
204
     * @return array
205
     */
206 4
    private function getRemoteDirectoryList() : array
207
    {
208 4
        return Util\Path::getDirectoryListFromAbsolutePath($this->remotePath->getPath());
209
    }
210
211
    /**
212
     * Creates collector for SFTP
213
     *
214
     * @param  \phpbu\App\Backup\Target $target
215
     * @return \phpbu\App\Backup\Collector
216
     * @throws \phpbu\App\Backup\Sync\Exception
217
     */
218 1
    protected function createCollector(Target $target): Collector
219
    {
220 1
        return new Collector\Sftp($target, $this->remotePath, $this->createClient());
221
    }
222
}
223