GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( c2615c...40c08e )
by François
02:11
created

OpenVpn::writeProcess()   C

Complexity

Conditions 8
Paths 16

Size

Total Lines 80
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 80
rs 6.0132
c 0
b 0
f 0
cc 8
eloc 55
nc 16
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 *  Copyright (C) 2016 SURFnet.
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as
7
 *  published by the Free Software Foundation, either version 3 of the
8
 *  License, or (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace SURFnet\VPN\Node;
20
21
use RuntimeException;
22
use SURFnet\VPN\Common\FileIO;
23
use SURFnet\VPN\Common\HttpClient\ServerClient;
24
use SURFnet\VPN\Common\ProfileConfig;
25
26
class OpenVpn
27
{
28
    /** @var string */
29
    private $vpnConfigDir;
30
31
    /** @var string */
32
    private $vpnTlsDir;
33
34
    public function __construct($vpnConfigDir, $vpnTlsDir)
35
    {
36
        FileIO::createDir($vpnConfigDir, 0700);
37
        $this->vpnConfigDir = $vpnConfigDir;
38
        FileIO::createDir($vpnTlsDir, 0700);
39
        $this->vpnTlsDir = $vpnTlsDir;
40
    }
41
42
    public function generateKeys(ServerClient $serverClient, $commonName, $dhSourceFile)
43
    {
44
        $certData = $serverClient->post('add_server_certificate', ['common_name' => $commonName]);
45
46
        $certFileMapping = [
47
            'ca' => sprintf('%s/ca.crt', $this->vpnTlsDir),
48
            'certificate' => sprintf('%s/server.crt', $this->vpnTlsDir),
49
            'private_key' => sprintf('%s/server.key', $this->vpnTlsDir),
50
            'ta' => sprintf('%s/ta.key', $this->vpnTlsDir),
51
        ];
52
53
        foreach ($certFileMapping as $k => $v) {
54
            FileIO::writeFile($v, $certData[$k], 0600);
55
        }
56
57
        // copy the DH parameter file
58
        $dhTargetFile = sprintf('%s/dh.pem', $this->vpnTlsDir);
59
        if (false === copy($dhSourceFile, $dhTargetFile)) {
60
            throw new RuntimeException('unable to copy DH file');
61
        }
62
    }
63
64
    public function writeProfile($instanceNumber, $instanceId, $profileId, ProfileConfig $profileConfig)
65
    {
66
        $range = new IP($profileConfig->getItem('range'));
67
        $range6 = new IP($profileConfig->getItem('range6'));
68
        $processCount = $profileConfig->getItem('processCount');
69
70
        $splitRange = $range->split($processCount);
71
        $splitRange6 = $range6->split($processCount);
72
73
        if ('auto' === $managementIp = $profileConfig->getItem('managementIp')) {
74
            $managementIp = sprintf('10.42.%d.%d', 100 + $instanceNumber, 100 + $profileConfig->getItem('profileNumber'));
75
        }
76
77
        $processConfig = [
78
            'managementIp' => $managementIp,
79
        ];
80
81
        for ($i = 0; $i < $processCount; ++$i) {
82
            list($proto, $port) = self::getProtoPortListen(
83
                $profileConfig->getItem('processCount'),
84
                $profileConfig->getItem('listen'),
85
                $profileConfig->getItem('portShare')
86
            )[$i];
87
88
            $processConfig['range'] = $splitRange[$i];
89
            $processConfig['range6'] = $splitRange6[$i];
90
            $processConfig['dev'] = sprintf('tun-%d-%d-%d', $instanceNumber, $profileConfig->getItem('profileNumber'), $i);
91
            $processConfig['proto'] = $proto;
92
            $processConfig['port'] = $port;
93
            $processConfig['local'] = $profileConfig->getItem('listen');
94
            $processConfig['managementPort'] = 11940 + $i;
95
            $processConfig['configName'] = sprintf(
96
                '%s-%s-%d.conf',
97
                $instanceId,
98
                $profileId,
99
                $i
100
            );
101
102
            $this->writeProcess($instanceId, $profileId, $profileConfig, $processConfig);
103
        }
104
    }
105
106
    public static function getVpnProto($listenAddress)
107
    {
108
        if (false !== strpos($listenAddress, ':')) {
109
            return ['udp6', 'tcp6-server'];
110
        }
111
112
        return ['udp', 'tcp-server'];
113
    }
114
115
    public static function getProtoPortListen($processCount, $listenAddress, $portShare)
116
    {
117
        $vpnProto = self::getVpnProto($listenAddress);
118
119
        switch ($processCount) {
120
            case 1:
121
                return [
122
                    [$vpnProto[0], 1194],
123
                ];
124
            case 2:
125
                return [
126
                    [$vpnProto[0], 1194],
127
                    [$vpnProto[1], $portShare ? 1194 : 443],
128
                ];
129
            case 4:
130
            default:
131
                return [
132
                    [$vpnProto[0], 1194],
133
                    [$vpnProto[0], 1195],
134
                    [$vpnProto[1], 1194],
135
                    [$vpnProto[1], $portShare ? 1195 : 443],
136
                ];
137
            case 8:
0 ignored issues
show
Unused Code introduced by
case 8: return array...rtShare ? 1196 : 443)); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
138
                return [
139
                    [$vpnProto[0], 1194],
140
                    [$vpnProto[0], 1195],
141
                    [$vpnProto[0], 1196],
142
                    [$vpnProto[0], 1197],
143
                    [$vpnProto[0], 1198],
144
                    [$vpnProto[1], 1194],
145
                    [$vpnProto[1], 1195],
146
                    [$vpnProto[1], $portShare ? 1196 : 443],
147
                ];
148
        }
149
    }
150
151
    private function writeProcess($instanceId, $profileId, ProfileConfig $profileConfig, array $processConfig)
152
    {
153
        $tlsDir = sprintf('tls/%s/%s', $instanceId, $profileId);
154
155
        $rangeIp = new IP($processConfig['range']);
156
        $range6Ip = new IP($processConfig['range6']);
157
158
        // static options
159
        $serverConfig = [
160
            '# OpenVPN Server Configuration',
161
            'verb 3',
162
            'dev-type tun',
163
            sprintf('user %s', $profileConfig->getItem('_user')),
164
            sprintf('group %s', $profileConfig->getItem('_group')),
165
            'topology subnet',
166
            'persist-key',
167
            'persist-tun',
168
            'keepalive 10 60',
169
            'comp-lzo no',
170
            'remote-cert-tls client',
171
            'tls-version-min 1.2',
172
            'tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384:TLS-ECDHE-RSA-WITH-AES-256-CBC-SHA384:TLS-ECDHE-ECDSA-WITH-AES-256-CBC-SHA384:TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256',
173
            'auth SHA256',
174
            'cipher AES-256-CBC',
175
            'client-connect /usr/libexec/vpn-server-node-client-connect',
176
            'client-disconnect /usr/libexec/vpn-server-node-client-disconnect',
177
            'push "comp-lzo no"',
178
            'push "explicit-exit-notify 3"',
179
            sprintf('ca %s/ca.crt', $tlsDir),
180
            sprintf('cert %s/server.crt', $tlsDir),
181
            sprintf('key %s/server.key', $tlsDir),
182
            sprintf('dh %s/dh.pem', $tlsDir),
183
            sprintf('tls-auth %s/ta.key 0', $tlsDir),
184
            sprintf('server %s %s', $rangeIp->getNetwork(), $rangeIp->getNetmask()),
185
            sprintf('server-ipv6 %s', $range6Ip->getAddressPrefix()),
186
            sprintf('max-clients %d', $rangeIp->getNumberOfHosts() - 1),
187
            sprintf('script-security %d', $profileConfig->getItem('twoFactor') ? 3 : 2),
188
            sprintf('dev %s', $processConfig['dev']),
189
            sprintf('port %d', $processConfig['port']),
190
            sprintf('management %s %d', $processConfig['managementIp'], $processConfig['managementPort']),
191
            sprintf('setenv INSTANCE_ID %s', $instanceId),
192
            sprintf('setenv PROFILE_ID %s', $profileId),
193
            sprintf('proto %s', $processConfig['proto']),
194
            sprintf('local %s', $processConfig['local']),
195
        ];
196
197
        if (!$profileConfig->getItem('enableLog')) {
198
            $serverConfig[] = 'log /dev/null';
199
        }
200
201
        if ('tcp-server' === $processConfig['proto'] || 'tcp6-server' === $processConfig['proto']) {
202
            $serverConfig[] = 'tcp-nodelay';
203
        }
204
205
        if ('udp' === $processConfig['proto'] || 'udp6' === $processConfig['proto']) {
206
            // notify the clients to reconnect when restarting OpenVPN on the server
207
            // OpenVPN server >= 2.4
208
            $serverConfig[] = 'explicit-exit-notify 1';
209
        }
210
211
        if ($profileConfig->getItem('twoFactor')) {
212
            $serverConfig[] = 'auth-gen-token';  // Added in OpenVPN 2.4
213
            $serverConfig[] = 'auth-user-pass-verify /usr/libexec/vpn-server-node-verify-otp via-env';
214
        }
215
216
        // Routes
217
        $serverConfig = array_merge($serverConfig, self::getRoutes($profileConfig));
218
219
        // DNS
220
        $serverConfig = array_merge($serverConfig, self::getDns($profileConfig));
221
222
        // Client-to-client
223
        $serverConfig = array_merge($serverConfig, self::getClientToClient($profileConfig));
224
225
        sort($serverConfig, SORT_STRING);
226
227
        $configFile = sprintf('%s/%s', $this->vpnConfigDir, $processConfig['configName']);
228
229
        FileIO::writeFile($configFile, implode(PHP_EOL, $serverConfig), 0600);
230
    }
231
232
    private static function getRoutes(ProfileConfig $profileConfig)
233
    {
234
        $routeConfig = [];
235
        if ($profileConfig->getItem('defaultGateway')) {
236
            $routeConfig[] = 'push "redirect-gateway def1 bypass-dhcp"';
237
238
            // for Windows clients we need this extra route to mark the TAP adapter as
239
            // trusted and as having "Internet" access to allow the user to set it to
240
            // "Home" or "Work" to allow accessing file shares and printers
241
            // NOTE: this will break OS X tunnelblick because on disconnect it will
242
            // remove all default routes, including the one set before the VPN
243
            // was brought up
244
            //$routeConfig[] = 'push "route 0.0.0.0 0.0.0.0"';
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
245
246
            // for iOS we need this OpenVPN 2.4 "ipv6" flag to redirect-gateway
247
            // See https://docs.openvpn.net/docs/openvpn-connect/openvpn-connect-ios-faq.html
248
            $routeConfig[] = 'push "redirect-gateway ipv6"';
249
250
            // we use 2000::/3 instead of ::/0 because it seems to break on native IPv6
251
            // networks where the ::/0 default route already exists
252
            // XXX: no longer needed in OpenVPN 2.4! But not all our clients are
253
            // up to date, e.g. NetAidKit...
254
            $routeConfig[] = 'push "route-ipv6 2000::/3"';
255
        } else {
256
            // there may be some routes specified, push those, and not the default
257
            foreach ($profileConfig->getSection('routes')->toArray() as $route) {
258
                $routeIp = new IP($route);
259
                if (6 === $routeIp->getFamily()) {
260
                    // IPv6
261
                    $routeConfig[] = sprintf('push "route-ipv6 %s"', $routeIp->getAddressPrefix());
262
                } else {
263
                    // IPv4
264
                    $routeConfig[] = sprintf('push "route %s %s"', $routeIp->getAddress(), $routeIp->getNetmask());
265
                }
266
            }
267
        }
268
269
        return $routeConfig;
270
    }
271
272
    private static function getDns(ProfileConfig $profileConfig)
273
    {
274
        // only push DNS if we are the default route
275
        if (!$profileConfig->getItem('defaultGateway')) {
276
            return [];
277
        }
278
279
        $dnsEntries = [];
280
        foreach ($profileConfig->getSection('dns')->toArray() as $dnsAddress) {
281
            // also add DNS6 for OpenVPN >= 2.4beta2
282
            if (false !== strpos($dnsAddress, ':')) {
283
                $dnsEntries[] = sprintf('push "dhcp-option DNS6 %s"', $dnsAddress);
284
                continue;
285
            }
286
            $dnsEntries[] = sprintf('push "dhcp-option DNS %s"', $dnsAddress);
287
        }
288
289
        // prevent DNS leakage on Windows
290
        $dnsEntries[] = 'push "block-outside-dns"';
291
292
        return $dnsEntries;
293
    }
294
295
    private static function getClientToClient(ProfileConfig $profileConfig)
296
    {
297
        if (!$profileConfig->getItem('clientToClient')) {
298
            return [];
299
        }
300
301
        $rangeIp = new IP($profileConfig->getItem('range'));
302
        $range6Ip = new IP($profileConfig->getItem('range6'));
303
304
        return [
305
            'client-to-client',
306
            sprintf('push "route %s %s"', $rangeIp->getAddress(), $rangeIp->getNetmask()),
307
            sprintf('push "route-ipv6 %s"', $range6Ip->getAddressPrefix()),
308
        ];
309
    }
310
}
311