2020-06-13 14:08:29 -04:00
package provider
import (
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
2020-07-26 12:07:06 +00:00
"github.com/qdm12/gluetun/internal/constants"
"github.com/qdm12/gluetun/internal/models"
2020-06-13 14:08:29 -04:00
"github.com/qdm12/golibs/crypto/random"
"github.com/qdm12/golibs/network"
)
type pia struct {
2020-08-25 19:38:50 -04:00
random random . Random
servers [ ] models . PIAServer
2020-06-13 14:08:29 -04:00
}
2020-08-25 19:38:50 -04:00
func newPrivateInternetAccess ( servers [ ] models . PIAServer ) * pia {
2020-06-13 14:08:29 -04:00
return & pia {
2020-08-25 19:38:50 -04:00
random : random . NewRandom ( ) ,
servers : servers ,
2020-07-13 23:34:03 +00:00
}
2020-06-13 14:08:29 -04:00
}
2020-07-23 01:46:28 +00:00
func ( p * pia ) filterServers ( region string ) ( servers [ ] models . PIAServer ) {
if len ( region ) == 0 {
return constants . PIAServers ( )
}
2020-06-13 14:08:29 -04:00
for _ , server := range constants . PIAServers ( ) {
2020-07-23 01:46:28 +00:00
if strings . EqualFold ( server . Region , region ) {
return [ ] models . PIAServer { server }
2020-06-13 14:08:29 -04:00
}
}
2020-07-23 01:46:28 +00:00
return nil
}
func ( p * pia ) GetOpenVPNConnections ( selection models . ServerSelection ) ( connections [ ] models . OpenVPNConnection , err error ) {
servers := p . filterServers ( selection . Region )
if len ( servers ) == 0 {
return nil , fmt . Errorf ( "no server found for region %q" , selection . Region )
2020-06-13 14:08:29 -04:00
}
2020-07-23 01:46:28 +00:00
2020-06-13 14:08:29 -04:00
var port uint16
switch selection . Protocol {
case constants . TCP :
switch selection . EncryptionPreset {
case constants . PIAEncryptionPresetNormal :
port = 502
case constants . PIAEncryptionPresetStrong :
port = 501
}
case constants . UDP :
switch selection . EncryptionPreset {
case constants . PIAEncryptionPresetNormal :
port = 1198
case constants . PIAEncryptionPresetStrong :
port = 1197
}
}
if port == 0 {
return nil , fmt . Errorf ( "combination of protocol %q and encryption %q does not yield any port number" , selection . Protocol , selection . EncryptionPreset )
}
2020-07-23 01:46:28 +00:00
for _ , server := range servers {
for _ , IP := range server . IPs {
if selection . TargetIP != nil {
if selection . TargetIP . Equal ( IP ) {
return [ ] models . OpenVPNConnection { { IP : IP , Port : port , Protocol : selection . Protocol } } , nil
}
} else {
connections = append ( connections , models . OpenVPNConnection { IP : IP , Port : port , Protocol : selection . Protocol } )
}
}
}
if selection . TargetIP != nil {
return nil , fmt . Errorf ( "target IP %s not found in IP addresses" , selection . TargetIP )
2020-06-13 14:08:29 -04:00
}
2020-07-23 01:46:28 +00:00
if len ( connections ) > 64 {
connections = connections [ : 64 ]
}
2020-06-13 14:08:29 -04:00
return connections , nil
}
2020-07-13 23:34:03 +00:00
func ( p * pia ) BuildConf ( connections [ ] models . OpenVPNConnection , verbosity , uid , gid int , root bool , cipher , auth string , extras models . ExtraConfigOptions ) ( lines [ ] string ) {
2020-06-13 14:08:29 -04:00
var X509CRL , certificate string
if extras . EncryptionPreset == constants . PIAEncryptionPresetNormal {
if len ( cipher ) == 0 {
cipher = "aes-128-cbc"
}
if len ( auth ) == 0 {
auth = "sha1"
}
X509CRL = constants . PiaX509CRLNormal
certificate = constants . PIACertificateNormal
} else { // strong encryption
if len ( cipher ) == 0 {
cipher = aes256cbc
}
if len ( auth ) == 0 {
auth = "sha256"
}
X509CRL = constants . PiaX509CRLStrong
certificate = constants . PIACertificateStrong
}
2020-07-13 23:34:03 +00:00
lines = [ ] string {
2020-06-13 14:08:29 -04:00
"client" ,
"dev tun" ,
"nobind" ,
"persist-key" ,
"remote-cert-tls server" ,
// PIA specific
"ping 300" , // Ping every 5 minutes to prevent a timeout error
"reneg-sec 0" ,
"compress" , // allow PIA server to choose the compression to use
// Added constant values
"auth-nocache" ,
"mute-replay-warnings" ,
"pull-filter ignore \"auth-token\"" , // prevent auth failed loops
"auth-retry nointeract" ,
"remote-random" ,
"suppress-timestamps" ,
// Modified variables
fmt . Sprintf ( "verb %d" , verbosity ) ,
fmt . Sprintf ( "auth-user-pass %s" , constants . OpenVPNAuthConf ) ,
2020-07-12 19:15:05 +00:00
fmt . Sprintf ( "proto %s" , connections [ 0 ] . Protocol ) ,
2020-06-13 14:08:29 -04:00
fmt . Sprintf ( "cipher %s" , cipher ) ,
fmt . Sprintf ( "auth %s" , auth ) ,
}
if strings . HasSuffix ( cipher , "-gcm" ) {
lines = append ( lines , "ncp-disable" )
}
if ! root {
lines = append ( lines , "user nonrootuser" )
}
for _ , connection := range connections {
2020-07-12 19:15:05 +00:00
lines = append ( lines , fmt . Sprintf ( "remote %s %d" , connection . IP , connection . Port ) )
2020-06-13 14:08:29 -04:00
}
lines = append ( lines , [ ] string {
"<crl-verify>" ,
"-----BEGIN X509 CRL-----" ,
X509CRL ,
"-----END X509 CRL-----" ,
"</crl-verify>" ,
} ... )
lines = append ( lines , [ ] string {
"<ca>" ,
"-----BEGIN CERTIFICATE-----" ,
certificate ,
"-----END CERTIFICATE-----" ,
"</ca>" ,
"" ,
} ... )
2020-07-13 23:34:03 +00:00
return lines
2020-06-13 14:08:29 -04:00
}
2020-07-13 23:34:03 +00:00
func ( p * pia ) GetPortForward ( client network . Client ) ( port uint16 , err error ) {
2020-06-13 14:08:29 -04:00
b , err := p . random . GenerateRandomBytes ( 32 )
if err != nil {
return 0 , err
}
clientID := hex . EncodeToString ( b )
url := fmt . Sprintf ( "%s/?client_id=%s" , constants . PIAPortForwardURL , clientID )
2020-07-13 23:34:03 +00:00
content , status , err := client . GetContent ( url ) // TODO add ctx
2020-06-13 14:08:29 -04:00
switch {
case err != nil :
return 0 , err
case status != http . StatusOK :
return 0 , fmt . Errorf ( "status is %d for %s; does your PIA server support port forwarding?" , status , url )
case len ( content ) == 0 :
return 0 , fmt . Errorf ( "port forwarding is already activated on this connection, has expired, or you are not connected to a PIA region that supports port forwarding" )
}
body := struct {
Port uint16 ` json:"port" `
} { }
if err := json . Unmarshal ( content , & body ) ; err != nil {
return 0 , fmt . Errorf ( "port forwarding response: %w" , err )
}
return body . Port , nil
}