Skip to content

Instantly share code, notes, and snippets.

@brianbruggeman
Last active May 31, 2024 09:02
Show Gist options
  • Save brianbruggeman/f032f5b8e4b7fc1c63c8691071be5946 to your computer and use it in GitHub Desktop.
Save brianbruggeman/f032f5b8e4b7fc1c63c8691071be5946 to your computer and use it in GitHub Desktop.
Convert Viscosity to Open VPN
Public Domain
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Converts viscosity export files into an open vpn package
Usage: viscosity-to-openvpn.py <input> <output>
"""
import io
import os
import sys
import tarfile
import click
if sys.version.startswith('3'):
unicode = str
# ----------------------------------------------------------------------
# Exceptions
# ----------------------------------------------------------------------
class ConversionError(Exception):
"""Base conversion error"""
pass
class NoConnectionName(ConversionError):
"""No connection name was available"""
pass
class NoCertificateData(ConversionError):
"""No certificate data was found within certificate file"""
pass
class NoCertificateFile(ConversionError):
"""File was not available within archive"""
pass
# ----------------------------------------------------------------------
# Command-line Interface
# ----------------------------------------------------------------------
@click.command()
@click.argument('input-path', type=click.Path(exists=True))
@click.argument('output', required=False, type=click.Path(), default=None)
def convert(input_path, output=None):
'''Converts Viscosity package
Args:
input (str): path to folder or file input
output (str): path to folder output [default: None]
'''
if input_path.endswith('.visc'):
output = input_path if output is None else output
if output and not os.path.exists(output):
output = input_path
files = [os.path.join(input_path, filename) for filename in os.listdir(input_path)]
for config_fp in files:
new_config = []
if config_fp.endswith('.conf'):
with io.open(config_fp, encoding='utf-8') as stream:
connection_name = extract(stream, new_config, input_path=input_path)
new_config.insert(0, '# OpenVPN Config for {}'.format(connection_name))
new_config = '\n'.join(new_config) + '\n'
output_filepath = os.path.join(output, '{}.ovpn'.format(connection_name))
with io.open(output_filepath, 'w', encoding='utf-8') as out:
out.write(unicode(new_config))
print('Wrote: {}'.format(output_filepath))
elif input_path.endswith('.visz'):
if output is None:
output = os.path.dirname(input_path)
data = {}
with tarfile.open(input_path) as zipped:
for filepath, fileinfo in zip(zipped.getnames(), zipped.getmembers()):
if not fileinfo.isfile():
continue
filename = filepath.split(os.path.sep)[-1]
data[filename] = zipped.extractfile(filepath).read()
for key in data:
if not key.endswith('.conf') or key.startswith('.'):
continue
new_config = []
lines = data[key].split('\n')
connection_name = extract(lines, new_config, file_data=data)
new_config.insert(0, '# OpenVPN Config for {}'.format(connection_name))
new_config = '\n'.join(new_config) + '\n'
output_filepath = os.path.join(output, '{}.ovpn'.format(connection_name))
with io.open(output_filepath, 'w', encoding='utf-8') as out:
out.write(unicode(new_config))
print('Wrote: {}'.format(output_filepath))
# ----------------------------------------------------------------------
# CLI Support
# ----------------------------------------------------------------------
def extract(data, new_config, input_path=None, file_data={}):
certificate_files = ['ca', 'cert', 'key', 'tls-auth']
connection_name = ''
for line in data:
line = line.rstrip()
if not line.strip():
continue
# This was an invalid configuration, for some reason
elif line == 'compress lzo':
continue
elif line.startswith('#'):
if line.startswith('#viscosity name'):
connection_name = line.split('#viscosity name ', 1)[-1]
connection_name = connection_name.strip()
continue
try:
key, value = line.split(' ', 1)
value = value.strip()
except ValueError:
key, value = line, ''
if key in certificate_files:
if key == 'tls-auth':
try:
value, direction = value.split(' ', 1)
new_config.append('key-direction {}'.format(direction))
except ValueError:
pass
if input_path:
cert_filepath = os.path.join(input_path, value)
with io.open(cert_filepath, encoding='utf-8') as cf:
certificate = cf.read()
else:
if value not in file_data:
raise NoCertificateFile('Could not find certificate file in archive')
certificate = file_data.get(value)
if not certificate:
raise NoCertificateData('Could not find certificate data')
new_config.append('<%s>' % key)
new_config.append(certificate)
new_config.append('</%s>' % key)
continue
new_config.append(line)
if not connection_name.strip():
raise NoConnectionName('Could not find connection name in file. Aborting')
return connection_name
if __name__ == '__main__':
convert()
@lukasz-tc
Copy link

Great job 🥇

@lpcvoid
Copy link

lpcvoid commented May 16, 2019

Thank you!

@brianbruggeman
Copy link
Author

Hello, sorry for the question but I would like to know how we use this script?
I am not familiar with python.
Thank.

Please look at this: https://www.amazon.com/Learn-Python-Hard-Way-Introduction/dp/0321884914

@bluephoenix71
Copy link

I have a visz and a visc file format for my viscosity vpn profile. I would just like to know what is the syntax in order to convert the visz and visc file so I can use it on my OpenVPN client?

Do I save this script with extension .py?
How Do I then point my visc/visz file to convert it to an OpenVPN file format.

@campbeln
Copy link

campbeln commented Feb 9, 2020

FYI: In Ubuntu, to install click, you cannot use pip3. Use the following to install click:

sudo apt install python-pip
pip install click

Then you'll be able to run the script above :)

@Caporeira
Copy link

Im getting error:
Error: Missing argument 'INPUT_PATH'.
In with line should i write the path ?

@brianbruggeman
Copy link
Author

Maybe try running:

python viscosity-to-openvpn.py  --help

@Caporeira
Copy link

I'm getting this error:

Traceback (most recent call last): File "viscosity-to-openvpn.py", line 162, in <module> convert() File "C:\Python38\lib\site-packages\click\core.py", line 829, in __call__ return self.main(*args, **kwargs) File "C:\Python38\lib\site-packages\click\core.py", line 782, in main rv = self.invoke(ctx) File "C:\Python38\lib\site-packages\click\core.py", line 1066, in invoke return ctx.invoke(self.callback, **ctx.params) File "C:\Python38\lib\site-packages\click\core.py", line 610, in invoke return callback(*args, **kwargs) File "viscosity-to-openvpn.py", line 88, in convert lines = data[key].split('\n') TypeError: a bytes-like object is required, not 'str'

@brianbruggeman
Copy link
Author

Makes sense. I think the original script may have been built in python2. You'll probably have to update for python 3.8.

There's a hint in that trackback (line 88: data[key].split('\n')) and TypeError: a bytes-like object is required....

Python 3 modified a bunch of APIs to use bytes rather than string. The reason for this is to make Python (by default) more compatible with globalization and unicode data. There are 7 forks for the original code. I suspect one of them has upgraded to python 3.8. But if you want to take a SWAG at fixing it, then:

I suspect that once you fix the above error, you'll have to dig through a few more related errors. But once you wade through it, you should have something working.

@zaidbinkokab
Copy link

Thank you so much for the script but having some problem.

TypeError: convert() got an unexpected keyword argument "/path to my .visc/" file.

What could be the possible solution for it. Your kind reply will be much appreciated. Thanks

@zaidbinkokab
Copy link

Traceback (most recent call last):
File "viscosity-to-openvpn.py", line 162, in
convert()
File "/opt/anaconda3/lib/python3.8/site-packages/click/core.py", line 829, in call
return self.main(*args, **kwargs)
File "/opt/anaconda3/lib/python3.8/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/opt/anaconda3/lib/python3.8/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/opt/anaconda3/lib/python3.8/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
TypeError: convert() got an unexpected keyword argument '/path to my .visc/'

Complete log for your perusal please.

@JohnWenk
Copy link

JohnWenk commented May 25, 2021

Thank you so much for the script but having some problem.

TypeError: convert() got an unexpected keyword argument "/path to my .visc/" file.

What could be the possible solution for it. Your kind reply will be much appreciated. Thanks

You have to specify the file path to your viscosity file, eg:
c:_TEMP>python viscosity-to-openvpn.py c:_TEMP\Your_viscosity_profile.visc

in this example, i have a folder '_TEMP' on C:
inside this folder is the python script located, aswell as the *.visc profile

@jlax47
Copy link

jlax47 commented Nov 2, 2021

Thank you for this, it was super helpful!

@vinicius795
Copy link

i update the code, https://gist.github.com/vinicius795/e975688fa8ffcba549d8240ecf0a7f9f now it works in python 3.10

@epsilon-pluto
Copy link

epsilon-pluto commented Feb 17, 2022

if __name__ == '__main__':
    preconvert()

def preconvert(input_path, output=None):
    if input_path.endswith('.visc') or input_path.endswith('.visz'):
        convert(input_path,output)
        return
        
    for myfile in os.listdir(input_path):
        if myfile.endswith('.visc') or myfile.endswith('.visz'):
            convert(os.path.join(input_path, myfile))

this change give ability to convert all files in a folder : python viscosity-to-openvpn.py clients/
at the same time you still cat convert just single file: viscosity-to-openvpn.py clients/client1.visz

@acmodeu
Copy link

acmodeu commented May 17, 2022

something still needs to be fixed:

python3 viscosity-to-openvpn.py ovpn.visz
Traceback (most recent call last):
  File "viscosity-to-openvpn.py", line 165, in <module>
    convert()
  File "/usr/lib/python3/dist-packages/click/core.py", line 764, in __call__
    return self.main(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/click/core.py", line 717, in main
    rv = self.invoke(ctx)
  File "/usr/lib/python3/dist-packages/click/core.py", line 956, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/usr/lib/python3/dist-packages/click/core.py", line 555, in invoke
    return callback(*args, **kwargs)
  File "viscosity-to-openvpn.py", line 81, in convert
    data[filename] = zipped.extractfile(filepath).read().decode('utf8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xab in position 45: invalid start byte

@thiagofigueiro
Copy link

This other python 2 script worked for me: https://gist.github.com/ishahid/693c2c97b3236a3c2416fc09ab170244

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment