module Ovpnmcgen

Constants

SUMMARY
VERSION

Public Class Methods

config() click to toggle source
# File lib/ovpnmcgen/config.rb, line 15
def config
  @@config
end
configure(filename = @@config_file_name) click to toggle source

attr_accessor :config, :config_file_name

# File lib/ovpnmcgen/config.rb, line 8
def configure(filename = @@config_file_name)

  @@config = AppConfiguration.new filename do
    prefix 'og'
  end
end
generate(inputs = {}) click to toggle source
# File lib/ovpnmcgen.rb, line 10
def generate(inputs = {})
  identifier = inputs[:identifier] || inputs[:host].split('.').reverse!.join('.')
  port = inputs[:port] || 1194
  certUUID = inputs[:cert_uuid] || SecureRandom.uuid.chomp.upcase
  vpnUUID = inputs[:vpn_uuid] || SecureRandom.uuid.chomp.upcase
  plistUUID = inputs[:profile_uuid] || SecureRandom.uuid.chomp.upcase
  user, device, domain, host, proto, enableVOD = inputs[:user], inputs[:device], inputs[:host], inputs[:host], inputs[:proto], inputs[:enableVOD]
  p12pass = inputs[:p12pass] || ''
  trusted_ssids = inputs[:trusted_ssids] || false
  untrusted_ssids = inputs[:untrusted_ssids] || false
  remotes = inputs[:remotes] || false
  vodDomains = inputs[:domains] || false
  vpnName = inputs[:vpn_name] || "#{host}/VoD"
  plistDescription = "OpenVPN Configuration Payload for #{user}-#{device}@#{host}"

  # Ensure [un]trusted_ssids are Arrays.
  trusted_ssids = Array(trusted_ssids) if trusted_ssids
  untrusted_ssids = Array(untrusted_ssids) if untrusted_ssids
  remotes = Array(remotes) if remotes
  vodDomains = Array(vodDomains) if vodDomains

  begin
    ca_cert = File.readlines(inputs[:cafile]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "CA file not found: #{inputs[:cafile]}!"
    exit
  end

  begin
    tls_crypt = File.readlines(inputs[:tlscryptfile]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "TLS crypt file not found: #{inputs[:tlscryptfile]}!"
    exit
  end if inputs[:tlscryptfile]

  begin
    tls_auth = File.readlines(inputs[:tafile]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "TLS file not found: #{inputs[:tafile]}!"
    exit
  end if inputs[:tafile]

  begin
    cert_file = File.readlines(inputs[:cert]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "Cert file not found: #{inputs[:cert]}!"
    exit
  end if inputs[:cert]

  begin
    key_file = File.readlines(inputs[:key]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "Key file not found: #{inputs[:key]}!"
    exit
  end if inputs[:key]

  begin
    p12file = Base64.encode64(File.read(inputs[:p12file]))
  rescue Errno::ENOENT
    puts "PKCS#12 file not found: #{inputs[:p12file]}!"
    exit
  end if inputs[:p12file]

  unless inputs[:ovpnconfigfile].nil?
    ovpnconfighash = Ovpnmcgen.getOVPNVendorConfigHash(inputs[:ovpnconfigfile])
    plistDescription = "#{plistDescription}. Includes custom OpenVPN directives #{ovpnconfighash.to_s.gsub('"', '').gsub('=>', '=')}."
  else # Bare minimum configuration
    ovpnconfighash = {
      'client' => 'NOARGS',
      'comp-lzo' => 'NOARGS',
      'dev' => 'tun',
      'remote-cert-tls' => 'server'
    }
  end
  if remotes
    ovpnconfighash['remote.1'] = "#{host} #{port} #{proto}"
    remotes.each_with_index do |r, i|
      ovpnconfighash["remote.#{i+2}"] = r
    end
  else
    ovpnconfighash['remote'] = "#{host} #{port} #{proto}"
  end
  ovpnconfighash['ca'] = ca_cert
  ovpnconfighash['tls-auth'] = tls_auth if inputs[:tafile]
  ovpnconfighash['key-direction'] = '1' if inputs[:tafile]
  ovpnconfighash['tls-crypt'] = tls_crypt if inputs[:tlscryptfile]
  ovpnconfighash['cert'] = cert_file if inputs[:cert]
  ovpnconfighash['key'] = key_file if inputs[:key]
  ovpnconfighash['vpn-on-demand'] = '0' unless enableVOD

  vpnOnDemandRules = Array.new
  vodTrusted = { # Trust only Wifi SSID
    'InterfaceTypeMatch' => 'WiFi',
    'SSIDMatch' => trusted_ssids,
    'Action' => 'Disconnect'
  }
  vodUntrusted = { # Untrust Wifi
    'InterfaceTypeMatch' => 'WiFi',
    'SSIDMatch' => untrusted_ssids,
    'Action' => 'Connect'
  }
  vodWifiOnly = { # Untrust all Wifi
    'InterfaceTypeMatch' => 'WiFi',
    'Action' => case inputs[:security_level]
      when 'paranoid', 'high'
        'Connect'
      else # medium
        'Ignore'
      end
  }

  vodDomainOnlyActionParam = {
    'Domains' => vodDomains,
    'DomainAction' => 'ConnectIfNeeded'
  }
  vodDomainOnlyActionParam['RequiredURLStringProbe'] = inputs[:domain_probe_url] if inputs[:domain_probe_url]

  vodDomainOnly = { # When a domain is searched, bring up VPN
    'Action' => 'EvaluateConnection',
    #'DNSDomainMatch' => vodDomains # this key only works for configured DNS domains search list.
    'ActionParameters' => [vodDomainOnlyActionParam]
  }

  vodCellularOnly = { # Trust Cellular
    'InterfaceTypeMatch' => 'Cellular',
    'Action' => case inputs[:security_level]
      when 'paranoid'
        'Connect'
      else # high, medium
        'Ignore'
      end
  }

  # Default catch-all to prevent circular race.
  vodDefault = { # Default catch-all
    'Action' => 'Ignore'
  }

  # Insert URLStringProbe conditions when enabled with --url-probe.
  vodTrusted['URLStringProbe'] =
    vodUntrusted['URLStringProbe'] =
    vodWifiOnly['URLStringProbe'] =
    vodDomainOnly['URLStringProbe'] =
    vodCellularOnly['URLStringProbe'] =
    vodDefault['URLStringProbe'] =
    inputs[:url_probe] if inputs[:url_probe]

  # Insert trusted SSIDs-specific URLStringProbe condition when enabled with --trusted-ssids-url-probe.
  vodTrusted['URLStringProbe'] = inputs[:trusted_ssids_probe_url] if inputs[:trusted_ssids_probe_url]

  vpnOnDemandRules << vodTrusted if trusted_ssids
  vpnOnDemandRules << vodUntrusted if untrusted_ssids
  vpnOnDemandRules << vodWifiOnly
  vpnOnDemandRules << vodDomainOnly if vodDomains
  vpnOnDemandRules << vodCellularOnly << vodDefault
  vpnOnDemandRules << { # Default catch-all when URLStringProbe is enabled and returns false to prevent circular race.
    'Action' => 'Ignore'
    } if inputs[:url_probe]

  cert = {
    'Password' => p12pass,
    'PayloadCertificateFileName' => "#{user}-#{device}.p12",
    'PayloadContent' => StringData.new(p12file),
    'PayloadDescription' => 'Provides device authentication (certificate or identity).',
    'PayloadDisplayName' => "#{user}-#{device}.p12",
    'PayloadIdentifier' => (inputs[:cert_uuid]) ? "com.apple.vpn.managed.#{certUUID}" : "#{identifier}.#{user}-#{device}.credential",
    'PayloadOrganization' => domain,
    'PayloadType' => 'com.apple.security.pkcs12',
    'PayloadUUID' => certUUID,
    'PayloadVersion' => 1
  } if p12file

  vpn = {
    'PayloadDescription' => "Configures VPN settings, including authentication.",
    'PayloadDisplayName' => "VPN (#{host}/VoD)",
    'PayloadIdentifier' => (inputs[:vpn_uuid]) ? "com.apple.vpn.managed.#{certUUID}" : "#{identifier}.#{user}-#{device}.vpnconfig",
    'PayloadOrganization' => domain,
    'PayloadType' => 'com.apple.vpn.managed',
    'PayloadUUID' => vpnUUID,
    'PayloadVersion' => 1,
    'UserDefinedName' => vpnName,
    'VPN' => {
      'AuthenticationMethod' => 'Certificate',
      'OnDemandEnabled' => (enableVOD)? 1 : 0,
      'OnDemandRules' => vpnOnDemandRules,
      'PayloadCertificateUUID' => certUUID,
      'RemoteAddress' => 'DEFAULT'
    },
    'VPNSubType' => (inputs[:v12compat])? 'net.openvpn.connect.app' : 'net.openvpn.OpenVPN-Connect.vpnplugin',
    'VPNType' => 'VPN',
    'VendorConfig' => ovpnconfighash
  }
  unless p12file
    vpn['VPN']['AuthName'] = "#{user}-#{device}"
    vpn['VPN']['AuthenticationMethod'] = 'Password'
    vpn['VPN'].delete('PayloadCertificateUUID')
  end
  if inputs[:idle_timer]
    vpn['VPN']['DisconnectOnIdle'] = 1
    vpn['VPN']['DisconnectOnIdleTimer'] = inputs[:idle_timer]
  end

  plistPayloadContent = [vpn]
  plistPayloadContent << cert if p12file
  #encPlistPayloadContent = cmsEncrypt([vpn, cert].to_plist).der_format

  plist = {
    'PayloadDescription' => plistDescription,
    'PayloadDisplayName' => "#{host} OpenVPN #{user}@#{device}",
    'PayloadIdentifier' => (inputs[:profile_uuid]) ? "com.apple.vpn.managed.#{plistUUID}" : "#{identifier}.#{user}-#{device}",
    'PayloadOrganization' => domain,
    'PayloadRemovalDisallowed' => false,
    'PayloadType' => 'Configuration',
    'PayloadUUID' => plistUUID,
    'PayloadVersion' => 1,
    #'EncryptedPayloadContent' => StringData.new(encPlistPayloadContent)
    'PayloadContent' => plistPayloadContent
  }

  return plist.to_plist
end
getOVPNVendorConfigHash(ovpnfilepath) click to toggle source
# File lib/ovpnmcgen/ovpnconfig.rb, line 3
def getOVPNVendorConfigHash(ovpnfilepath)
  ovpnfile = ""
  begin
    # get rid of comments in the file.
    ovpnfile = File.readlines(ovpnfilepath).delete_if { |l| ; l.start_with?('#') or l.start_with?(';') or l.chomp.empty? }

    # TODO: [Warning] Get rid of/handle <ca>...</ca>, <cert>...</cert>, <key>...</key>, <tls-auth>...</tls-auth> inline cert/key enclosures.
    # Bail when inline cert/key enclosures are detected.
    ovpnfile.each do |l|
      r = l.chomp.match(/<.*>/)
      raise ArgumentError.new "OpenVPN client config file contains inline data enclosures: #{r.to_a.join(', ')}!\nSuch files are not yet supported. Remove them and try again" unless r.nil?
    end

    # TODO: Handle multiple remote lines.
    # Currently, all remote lines are ignored.

    # map to key => value pairs for plist purposes. Singular verbs will be: 'verb' => 'NOARGS'.
    ovpnhash = Hash[ovpnfile.map do |l|
      a = l.split
      if a.length == 1
        a << "NOARGS"
      elsif a.length > 2
        b = a.take(1)
        c = a.drop(1).join ' '
        a.replace(b << c)
      end
      a
    end]

    # delete obviously unsupported keys.
    ovpnhash.delete_if do |key, value|
      case key
      when 'fragment', 'mssfix', 'secret', 'socks-proxy', 'persist-key', 'persist-tun', 'resolv-retry', 'nobind', 'verb', 'user', 'group', 'pull', 'mute'
        true
      when 'remote', 'ca', 'pkcs12', 'tls-auth', 'tls-crypt', 'cert', 'key', 'proto' # specified with switches.
        true
      else
        false
      end
    end
  rescue Errno::ENOENT
    puts "OpenVPN config file not found: #{ovpnfilepath}!"
    exit
  end
  return ovpnhash
end

Private Instance Methods

config() click to toggle source
# File lib/ovpnmcgen/config.rb, line 15
def config
  @@config
end
configure(filename = @@config_file_name) click to toggle source

attr_accessor :config, :config_file_name

# File lib/ovpnmcgen/config.rb, line 8
def configure(filename = @@config_file_name)

  @@config = AppConfiguration.new filename do
    prefix 'og'
  end
end
generate(inputs = {}) click to toggle source
# File lib/ovpnmcgen.rb, line 10
def generate(inputs = {})
  identifier = inputs[:identifier] || inputs[:host].split('.').reverse!.join('.')
  port = inputs[:port] || 1194
  certUUID = inputs[:cert_uuid] || SecureRandom.uuid.chomp.upcase
  vpnUUID = inputs[:vpn_uuid] || SecureRandom.uuid.chomp.upcase
  plistUUID = inputs[:profile_uuid] || SecureRandom.uuid.chomp.upcase
  user, device, domain, host, proto, enableVOD = inputs[:user], inputs[:device], inputs[:host], inputs[:host], inputs[:proto], inputs[:enableVOD]
  p12pass = inputs[:p12pass] || ''
  trusted_ssids = inputs[:trusted_ssids] || false
  untrusted_ssids = inputs[:untrusted_ssids] || false
  remotes = inputs[:remotes] || false
  vodDomains = inputs[:domains] || false
  vpnName = inputs[:vpn_name] || "#{host}/VoD"
  plistDescription = "OpenVPN Configuration Payload for #{user}-#{device}@#{host}"

  # Ensure [un]trusted_ssids are Arrays.
  trusted_ssids = Array(trusted_ssids) if trusted_ssids
  untrusted_ssids = Array(untrusted_ssids) if untrusted_ssids
  remotes = Array(remotes) if remotes
  vodDomains = Array(vodDomains) if vodDomains

  begin
    ca_cert = File.readlines(inputs[:cafile]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "CA file not found: #{inputs[:cafile]}!"
    exit
  end

  begin
    tls_crypt = File.readlines(inputs[:tlscryptfile]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "TLS crypt file not found: #{inputs[:tlscryptfile]}!"
    exit
  end if inputs[:tlscryptfile]

  begin
    tls_auth = File.readlines(inputs[:tafile]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "TLS file not found: #{inputs[:tafile]}!"
    exit
  end if inputs[:tafile]

  begin
    cert_file = File.readlines(inputs[:cert]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "Cert file not found: #{inputs[:cert]}!"
    exit
  end if inputs[:cert]

  begin
    key_file = File.readlines(inputs[:key]).map { |x| x.chomp }.join('\n')
  rescue Errno::ENOENT
    puts "Key file not found: #{inputs[:key]}!"
    exit
  end if inputs[:key]

  begin
    p12file = Base64.encode64(File.read(inputs[:p12file]))
  rescue Errno::ENOENT
    puts "PKCS#12 file not found: #{inputs[:p12file]}!"
    exit
  end if inputs[:p12file]

  unless inputs[:ovpnconfigfile].nil?
    ovpnconfighash = Ovpnmcgen.getOVPNVendorConfigHash(inputs[:ovpnconfigfile])
    plistDescription = "#{plistDescription}. Includes custom OpenVPN directives #{ovpnconfighash.to_s.gsub('"', '').gsub('=>', '=')}."
  else # Bare minimum configuration
    ovpnconfighash = {
      'client' => 'NOARGS',
      'comp-lzo' => 'NOARGS',
      'dev' => 'tun',
      'remote-cert-tls' => 'server'
    }
  end
  if remotes
    ovpnconfighash['remote.1'] = "#{host} #{port} #{proto}"
    remotes.each_with_index do |r, i|
      ovpnconfighash["remote.#{i+2}"] = r
    end
  else
    ovpnconfighash['remote'] = "#{host} #{port} #{proto}"
  end
  ovpnconfighash['ca'] = ca_cert
  ovpnconfighash['tls-auth'] = tls_auth if inputs[:tafile]
  ovpnconfighash['key-direction'] = '1' if inputs[:tafile]
  ovpnconfighash['tls-crypt'] = tls_crypt if inputs[:tlscryptfile]
  ovpnconfighash['cert'] = cert_file if inputs[:cert]
  ovpnconfighash['key'] = key_file if inputs[:key]
  ovpnconfighash['vpn-on-demand'] = '0' unless enableVOD

  vpnOnDemandRules = Array.new
  vodTrusted = { # Trust only Wifi SSID
    'InterfaceTypeMatch' => 'WiFi',
    'SSIDMatch' => trusted_ssids,
    'Action' => 'Disconnect'
  }
  vodUntrusted = { # Untrust Wifi
    'InterfaceTypeMatch' => 'WiFi',
    'SSIDMatch' => untrusted_ssids,
    'Action' => 'Connect'
  }
  vodWifiOnly = { # Untrust all Wifi
    'InterfaceTypeMatch' => 'WiFi',
    'Action' => case inputs[:security_level]
      when 'paranoid', 'high'
        'Connect'
      else # medium
        'Ignore'
      end
  }

  vodDomainOnlyActionParam = {
    'Domains' => vodDomains,
    'DomainAction' => 'ConnectIfNeeded'
  }
  vodDomainOnlyActionParam['RequiredURLStringProbe'] = inputs[:domain_probe_url] if inputs[:domain_probe_url]

  vodDomainOnly = { # When a domain is searched, bring up VPN
    'Action' => 'EvaluateConnection',
    #'DNSDomainMatch' => vodDomains # this key only works for configured DNS domains search list.
    'ActionParameters' => [vodDomainOnlyActionParam]
  }

  vodCellularOnly = { # Trust Cellular
    'InterfaceTypeMatch' => 'Cellular',
    'Action' => case inputs[:security_level]
      when 'paranoid'
        'Connect'
      else # high, medium
        'Ignore'
      end
  }

  # Default catch-all to prevent circular race.
  vodDefault = { # Default catch-all
    'Action' => 'Ignore'
  }

  # Insert URLStringProbe conditions when enabled with --url-probe.
  vodTrusted['URLStringProbe'] =
    vodUntrusted['URLStringProbe'] =
    vodWifiOnly['URLStringProbe'] =
    vodDomainOnly['URLStringProbe'] =
    vodCellularOnly['URLStringProbe'] =
    vodDefault['URLStringProbe'] =
    inputs[:url_probe] if inputs[:url_probe]

  # Insert trusted SSIDs-specific URLStringProbe condition when enabled with --trusted-ssids-url-probe.
  vodTrusted['URLStringProbe'] = inputs[:trusted_ssids_probe_url] if inputs[:trusted_ssids_probe_url]

  vpnOnDemandRules << vodTrusted if trusted_ssids
  vpnOnDemandRules << vodUntrusted if untrusted_ssids
  vpnOnDemandRules << vodWifiOnly
  vpnOnDemandRules << vodDomainOnly if vodDomains
  vpnOnDemandRules << vodCellularOnly << vodDefault
  vpnOnDemandRules << { # Default catch-all when URLStringProbe is enabled and returns false to prevent circular race.
    'Action' => 'Ignore'
    } if inputs[:url_probe]

  cert = {
    'Password' => p12pass,
    'PayloadCertificateFileName' => "#{user}-#{device}.p12",
    'PayloadContent' => StringData.new(p12file),
    'PayloadDescription' => 'Provides device authentication (certificate or identity).',
    'PayloadDisplayName' => "#{user}-#{device}.p12",
    'PayloadIdentifier' => (inputs[:cert_uuid]) ? "com.apple.vpn.managed.#{certUUID}" : "#{identifier}.#{user}-#{device}.credential",
    'PayloadOrganization' => domain,
    'PayloadType' => 'com.apple.security.pkcs12',
    'PayloadUUID' => certUUID,
    'PayloadVersion' => 1
  } if p12file

  vpn = {
    'PayloadDescription' => "Configures VPN settings, including authentication.",
    'PayloadDisplayName' => "VPN (#{host}/VoD)",
    'PayloadIdentifier' => (inputs[:vpn_uuid]) ? "com.apple.vpn.managed.#{certUUID}" : "#{identifier}.#{user}-#{device}.vpnconfig",
    'PayloadOrganization' => domain,
    'PayloadType' => 'com.apple.vpn.managed',
    'PayloadUUID' => vpnUUID,
    'PayloadVersion' => 1,
    'UserDefinedName' => vpnName,
    'VPN' => {
      'AuthenticationMethod' => 'Certificate',
      'OnDemandEnabled' => (enableVOD)? 1 : 0,
      'OnDemandRules' => vpnOnDemandRules,
      'PayloadCertificateUUID' => certUUID,
      'RemoteAddress' => 'DEFAULT'
    },
    'VPNSubType' => (inputs[:v12compat])? 'net.openvpn.connect.app' : 'net.openvpn.OpenVPN-Connect.vpnplugin',
    'VPNType' => 'VPN',
    'VendorConfig' => ovpnconfighash
  }
  unless p12file
    vpn['VPN']['AuthName'] = "#{user}-#{device}"
    vpn['VPN']['AuthenticationMethod'] = 'Password'
    vpn['VPN'].delete('PayloadCertificateUUID')
  end
  if inputs[:idle_timer]
    vpn['VPN']['DisconnectOnIdle'] = 1
    vpn['VPN']['DisconnectOnIdleTimer'] = inputs[:idle_timer]
  end

  plistPayloadContent = [vpn]
  plistPayloadContent << cert if p12file
  #encPlistPayloadContent = cmsEncrypt([vpn, cert].to_plist).der_format

  plist = {
    'PayloadDescription' => plistDescription,
    'PayloadDisplayName' => "#{host} OpenVPN #{user}@#{device}",
    'PayloadIdentifier' => (inputs[:profile_uuid]) ? "com.apple.vpn.managed.#{plistUUID}" : "#{identifier}.#{user}-#{device}",
    'PayloadOrganization' => domain,
    'PayloadRemovalDisallowed' => false,
    'PayloadType' => 'Configuration',
    'PayloadUUID' => plistUUID,
    'PayloadVersion' => 1,
    #'EncryptedPayloadContent' => StringData.new(encPlistPayloadContent)
    'PayloadContent' => plistPayloadContent
  }

  return plist.to_plist
end
getOVPNVendorConfigHash(ovpnfilepath) click to toggle source
# File lib/ovpnmcgen/ovpnconfig.rb, line 3
def getOVPNVendorConfigHash(ovpnfilepath)
  ovpnfile = ""
  begin
    # get rid of comments in the file.
    ovpnfile = File.readlines(ovpnfilepath).delete_if { |l| ; l.start_with?('#') or l.start_with?(';') or l.chomp.empty? }

    # TODO: [Warning] Get rid of/handle <ca>...</ca>, <cert>...</cert>, <key>...</key>, <tls-auth>...</tls-auth> inline cert/key enclosures.
    # Bail when inline cert/key enclosures are detected.
    ovpnfile.each do |l|
      r = l.chomp.match(/<.*>/)
      raise ArgumentError.new "OpenVPN client config file contains inline data enclosures: #{r.to_a.join(', ')}!\nSuch files are not yet supported. Remove them and try again" unless r.nil?
    end

    # TODO: Handle multiple remote lines.
    # Currently, all remote lines are ignored.

    # map to key => value pairs for plist purposes. Singular verbs will be: 'verb' => 'NOARGS'.
    ovpnhash = Hash[ovpnfile.map do |l|
      a = l.split
      if a.length == 1
        a << "NOARGS"
      elsif a.length > 2
        b = a.take(1)
        c = a.drop(1).join ' '
        a.replace(b << c)
      end
      a
    end]

    # delete obviously unsupported keys.
    ovpnhash.delete_if do |key, value|
      case key
      when 'fragment', 'mssfix', 'secret', 'socks-proxy', 'persist-key', 'persist-tun', 'resolv-retry', 'nobind', 'verb', 'user', 'group', 'pull', 'mute'
        true
      when 'remote', 'ca', 'pkcs12', 'tls-auth', 'tls-crypt', 'cert', 'key', 'proto' # specified with switches.
        true
      else
        false
      end
    end
  rescue Errno::ENOENT
    puts "OpenVPN config file not found: #{ovpnfilepath}!"
    exit
  end
  return ovpnhash
end