├── .gitignore ├── LICENSE.md ├── README.md ├── alembic ├── env.py ├── script.py.mako └── versions │ ├── 12e8cae9c38_add_vpnsessions_tabl.py │ ├── 1deecd313c4_initial.py │ ├── 25883baca06_remove_api_access_to.py │ ├── 2e6851ff437_add_last_notice_and_.py │ ├── 31cde2cf5e6_add_default_profile_for_existing_users.py │ ├── 33f748cdc2f_add_gateways_table.py │ ├── 42a3f495ec4_change_bandwith_size.py │ ├── 4396820d8f4_update_profile_table.py │ ├── 44db46b919f_add_pwresettoken.py │ ├── 516b5a4829e_add_tickets_table.py │ ├── 956b1aac1_add_giftcodes_free_o.py │ └── a8195fe5a5_add_referrer.py ├── ccvpn ├── __init__.py ├── filters.py ├── locale │ ├── ccvpn.pot │ ├── en │ │ └── LC_MESSAGES │ │ │ └── ccvpn.po │ └── fr │ │ └── LC_MESSAGES │ │ └── ccvpn.po ├── methods.py ├── models │ ├── __init__.py │ ├── base.py │ ├── icinga.py │ ├── paypal.py │ ├── sql.py │ └── types.py ├── pages │ ├── api.md │ ├── faq.fr.md │ ├── faq.md │ ├── help.fr.md │ ├── help.md │ ├── install-android.fr.md │ ├── install-android.md │ ├── install-gnulinux.fr.md │ ├── install-gnulinux.md │ ├── install-osx.fr.md │ ├── install-osx.md │ ├── install-windows.fr.md │ ├── install-windows.md │ ├── self-diagnosis.fr.md │ ├── self-diagnosis.md │ ├── tech.md │ └── tos.md ├── scripts │ ├── __init__.py │ ├── apiacl.py │ ├── checkbtcorders.py │ ├── expire_mail.py │ ├── initializedb.py │ └── mail.py ├── static │ ├── 7proxies.png │ ├── OpenSans-Extrabold.woff │ ├── OpenSans.woff │ ├── anon.svg │ ├── bg-logo.svg │ ├── bg-title.png │ ├── bg.png │ ├── bolt.svg │ ├── cheap.svg │ ├── fast.svg │ ├── openvpn.svg │ ├── ping.js │ ├── pygal │ │ ├── pygal-tooltips.js │ │ └── svg.jquery.js │ ├── reset.css │ ├── style.css │ └── unlimited.svg ├── subscribers.py ├── templates │ ├── account │ │ ├── index.mako │ │ ├── layout.mako │ │ ├── logs.mako │ │ ├── orders.mako │ │ ├── profiles_edit.mako │ │ └── settings.mako │ ├── admin │ │ ├── add.mako │ │ ├── home.mako │ │ ├── item.mako │ │ ├── layout.mako │ │ ├── list.mako │ │ ├── order_payment.mako │ │ ├── stats.mako │ │ └── user_actions.mako │ ├── config.ovpn.mako │ ├── forgot_password.mako │ ├── home.mako │ ├── layout.mako │ ├── login.mako │ ├── mail │ │ ├── expiry.mako │ │ ├── password_reset.mako │ │ ├── password_reset_done.mako │ │ └── tickets_updated.mako │ ├── order.mako │ ├── page.mako │ ├── reset_password.mako │ ├── signup.mako │ ├── status.mako │ └── tickets │ │ ├── list.mako │ │ ├── new.mako │ │ └── view.mako ├── tests │ ├── __init__.py │ ├── filters.py │ ├── models.py │ ├── scripts.py │ ├── views_account.py │ ├── views_api.py │ └── views_order.py └── views │ ├── __init__.py │ ├── account.py │ ├── admin.py │ ├── api.py │ ├── order.py │ └── tickets.py ├── cron_checkbtcorders.sh ├── development.ini ├── production.ini ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | .tmp/ 4 | *.swp 5 | ca.crt 6 | *.egg-info 7 | *.log 8 | *.mo 9 | openvpn/settings.py 10 | openvpn/config/ 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cognitive Cryptography 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CCrypto VPN 2 | =========== 3 | 4 | CCVPN is all the software we use at CCrypto to provide our VPN. 5 | 6 | It is made of a Pyramid web app you can see live at 7 | http://vpn.ccrypto.org/ including an API, and OpenVPN config/scripts that 8 | checks user accounts and passwords with the API. 9 | 10 | CCrypto's commercial support *does not* include this product. However, feel free to contact us and ask if you are having troubles running ccvpn, but without any guarantee. 11 | 12 | Getting Started 13 | --------------- 14 | 15 | ```bash 16 | git clone https://github.com/CCrypto/ccvpn.git 17 | cd ccvpn/ 18 | 19 | pyvenv ccvpn_env 20 | source ./ccvpn_env/bin/activate 21 | ./setup.py develop 22 | 23 | # Prepare the database 24 | initialize_ccvpn_db development.ini 25 | 26 | # Run the dev web server 27 | pserve development.ini 28 | ``` 29 | 30 | The default "admin" account has the password "admin". 31 | 32 | OpenVPN 33 | ------- 34 | 35 | ```bash 36 | # Generate a secret token for your OpenVPN server 37 | ccvpn_apiacl ./development.ini add localhost 38 | 39 | cd openvpn/ 40 | 41 | # You can copy and customize settings.py, or use this script: 42 | ./configure.py 43 | 44 | # run make.py. it renders config files to config/ for each profile 45 | ./make.py 46 | 47 | # On a standard GNU/Linux distribution, OpenVPN config files are stored in 48 | # /etc/openvpn. Copy them or make links: 49 | ln -s $(pwd)/config/alpha.conf /etc/openvpn/ccvpn-alpha.conf 50 | ln -s $(pwd)/config/beta.conf /etc/openvpn/ccvpn-beta.conf 51 | 52 | # On Debian/Ubuntu: 53 | service openvpn start ccvpn-alpha 54 | service openvpn start ccvpn-beta 55 | ``` 56 | 57 | Bitcoin Payments 58 | ---------------- 59 | 60 | You will need to run a script regularly to check for verified transaction. 61 | With this app installed in /home/vpn/ccvpn/, add this in your crontab: 62 | 63 | */5 * * * * /home/vpn/ccvpn/cron_checkbtcorders.sh /home/vpn/ccvpn/development.ini 64 | 65 | -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | from alembic import context 3 | from sqlalchemy import engine_from_config, pool 4 | from logging.config import fileConfig 5 | 6 | # this is the Alembic Config object, which provides 7 | # access to the values within the .ini file in use. 8 | config = context.config 9 | 10 | # Interpret the config file for Python logging. 11 | # This line sets up loggers basically. 12 | fileConfig(config.config_file_name) 13 | 14 | # Load models 15 | from ccvpn.models import Base 16 | target_metadata = Base.metadata 17 | 18 | # We get database settings from the same section as pyramid, instead of the 19 | # default 'alembic' section. Will not be used for other settings. 20 | # db_ini_section = config.config_ini_section 21 | db_ini_section = 'app:main' 22 | 23 | 24 | def run_migrations_offline(): 25 | """Run migrations in 'offline' mode. 26 | 27 | This configures the context with just a URL 28 | and not an Engine, though an Engine is acceptable 29 | here as well. By skipping the Engine creation 30 | we don't even need a DBAPI to be available. 31 | 32 | Calls to context.execute() here emit the given string to the 33 | script output. 34 | 35 | """ 36 | url = config.get_main_option('sqlalchemy.url', db_ini_section) 37 | context.configure(url=url, target_metadata=target_metadata) 38 | 39 | with context.begin_transaction(): 40 | context.run_migrations() 41 | 42 | 43 | def run_migrations_online(): 44 | """Run migrations in 'online' mode. 45 | 46 | In this scenario we need to create an Engine 47 | and associate a connection with the context. 48 | 49 | """ 50 | engine = engine_from_config(config.get_section(db_ini_section), 51 | prefix='sqlalchemy.', 52 | poolclass=pool.NullPool) 53 | 54 | connection = engine.connect() 55 | context.configure(connection=connection, target_metadata=target_metadata) 56 | 57 | try: 58 | with context.begin_transaction(): 59 | context.run_migrations() 60 | finally: 61 | connection.close() 62 | 63 | 64 | if context.is_offline_mode(): 65 | run_migrations_offline() 66 | else: 67 | run_migrations_online() 68 | 69 | -------------------------------------------------------------------------------- /alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = ${repr(up_revision)} 11 | down_revision = ${repr(down_revision)} 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | ${imports if imports else ""} 16 | 17 | def upgrade(): 18 | ${upgrades if upgrades else "pass"} 19 | 20 | 21 | def downgrade(): 22 | ${downgrades if downgrades else "pass"} 23 | -------------------------------------------------------------------------------- /alembic/versions/12e8cae9c38_add_vpnsessions_tabl.py: -------------------------------------------------------------------------------- 1 | """add vpnsessions table 2 | 3 | Revision ID: 12e8cae9c38 4 | Revises: 25883baca06 5 | Create Date: 2014-03-23 02:11:37.838134 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '12e8cae9c38' 11 | down_revision = '25883baca06' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.create_table('vpnsessions', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('gateway_id', sa.Integer(), nullable=False), 22 | sa.Column('gateway_version', sa.Integer(), nullable=False), 23 | sa.Column('user_id', sa.Integer(), nullable=False), 24 | sa.Column('profile_id', sa.Integer(), nullable=True), 25 | sa.Column('connect_date', sa.DateTime(), nullable=False), 26 | sa.Column('disconnect_date', sa.DateTime(), nullable=True), 27 | sa.Column('remote_addr', sa.String(), nullable=False), 28 | sa.Column('bytes_up', sa.Integer(), nullable=True), 29 | sa.Column('bytes_down', sa.Integer(), nullable=True), 30 | sa.ForeignKeyConstraint(['gateway_id'], ['gateways.id'], ), 31 | sa.ForeignKeyConstraint(['profile_id'], ['profiles.id'], ), 32 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 33 | sa.PrimaryKeyConstraint('id') 34 | ) 35 | ### end Alembic commands ### 36 | 37 | 38 | def downgrade(): 39 | ### commands auto generated by Alembic - please adjust! ### 40 | op.drop_table('vpnsessions') 41 | ### end Alembic commands ### 42 | -------------------------------------------------------------------------------- /alembic/versions/1deecd313c4_initial.py: -------------------------------------------------------------------------------- 1 | """initial 2 | 3 | Revision ID: 1deecd313c4 4 | Revises: None 5 | Create Date: 2014-01-05 06:22:41.816331 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '1deecd313c4' 11 | down_revision = None 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | pass 19 | 20 | 21 | def downgrade(): 22 | pass 23 | -------------------------------------------------------------------------------- /alembic/versions/25883baca06_remove_api_access_to.py: -------------------------------------------------------------------------------- 1 | """remove api access token table 2 | 3 | Revision ID: 25883baca06 4 | Revises: 33f748cdc2f 5 | Create Date: 2014-03-23 00:05:52.066581 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '25883baca06' 11 | down_revision = '33f748cdc2f' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | from sqlalchemy.dialects import postgresql 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.drop_table('apiaccesstok') 20 | ### end Alembic commands ### 21 | 22 | 23 | def downgrade(): 24 | ### commands auto generated by Alembic - please adjust! ### 25 | op.create_table('apiaccesstok', 26 | sa.Column('id', sa.INTEGER(), server_default="nextval('apiaccesstok_id_seq'::regclass)", nullable=False), 27 | sa.Column('token', sa.VARCHAR(length=32), autoincrement=False, nullable=False), 28 | sa.Column('label', sa.VARCHAR(length=256), autoincrement=False, nullable=True), 29 | sa.Column('remote_addr', postgresql.INET(), autoincrement=False, nullable=True), 30 | sa.Column('expire_date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True), 31 | sa.PrimaryKeyConstraint('id', name='apiaccesstok_pkey') 32 | ) 33 | ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /alembic/versions/2e6851ff437_add_last_notice_and_.py: -------------------------------------------------------------------------------- 1 | """add last notice and internal addresses 2 | 3 | Revision ID: 2e6851ff437 4 | Revises: 42a3f495ec4 5 | Create Date: 2014-06-05 21:40:47.132595 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '2e6851ff437' 11 | down_revision = '42a3f495ec4' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.add_column('users', sa.Column('last_expiry_notice', sa.DateTime(), nullable=True)) 20 | op.add_column('vpnsessions', sa.Column('internal_ip4', sa.String(), nullable=True)) 21 | op.add_column('vpnsessions', sa.Column('internal_ip6', sa.String(), nullable=True)) 22 | ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('vpnsessions', 'internal_ip6') 28 | op.drop_column('vpnsessions', 'internal_ip4') 29 | op.drop_column('users', 'last_expiry_notice') 30 | ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /alembic/versions/31cde2cf5e6_add_default_profile_for_existing_users.py: -------------------------------------------------------------------------------- 1 | """add default profile for existing users 2 | 3 | Revision ID: 31cde2cf5e6 4 | Revises: 4396820d8f4 5 | Create Date: 2014-11-04 21:03:53.093993 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '31cde2cf5e6' 11 | down_revision = '4396820d8f4' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | conn = op.get_bind() 19 | 20 | # Get a list of users without default profiles 21 | fq = conn.execute(''' 22 | select id 23 | from users 24 | where (select count(*) 25 | from profiles 26 | where profiles.uid=users.id and name='') = 0 27 | ''') 28 | users_without_dp = fq.fetchall() 29 | 30 | # Add one to them 31 | for user in users_without_dp: 32 | uid = user[0] 33 | 34 | iq = conn.execute(''' 35 | insert into profiles(uid, name) 36 | values(%d, '') 37 | ''' % uid) 38 | 39 | def downgrade(): 40 | pass 41 | -------------------------------------------------------------------------------- /alembic/versions/33f748cdc2f_add_gateways_table.py: -------------------------------------------------------------------------------- 1 | """add gateways table 2 | 3 | Revision ID: 33f748cdc2f 4 | Revises: 956b1aac1 5 | Create Date: 2014-03-22 23:28:27.605724 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '33f748cdc2f' 11 | down_revision = '956b1aac1' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.create_table('gateways', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('name', sa.String(length=32), nullable=False), 22 | sa.Column('isp_name', sa.String(length=32), nullable=False), 23 | sa.Column('isp_url', sa.String(), nullable=False), 24 | sa.Column('country', sa.String(length=2), nullable=False), 25 | sa.Column('token', sa.String(length=32), nullable=False), 26 | sa.Column('ipv4', sa.String(), nullable=True), 27 | sa.Column('ipv6', sa.String(), nullable=True), 28 | sa.Column('bps', sa.BigInteger(), nullable=True), 29 | sa.Column('enabled', sa.Boolean(), server_default='true', nullable=False), 30 | sa.PrimaryKeyConstraint('id') 31 | ) 32 | ### end Alembic commands ### 33 | 34 | 35 | def downgrade(): 36 | ### commands auto generated by Alembic - please adjust! ### 37 | op.drop_table('gateways') 38 | ### end Alembic commands ### 39 | -------------------------------------------------------------------------------- /alembic/versions/42a3f495ec4_change_bandwith_size.py: -------------------------------------------------------------------------------- 1 | """change bandwith size 2 | 3 | Revision ID: 42a3f495ec4 4 | Revises: 12e8cae9c38 5 | Create Date: 2014-04-21 19:23:04.632834 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '42a3f495ec4' 11 | down_revision = '12e8cae9c38' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | op.alter_column('vpnsessions', 'bytes_up', existing_type=sa.Integer(), type_=sa.BigInteger()) 19 | op.alter_column('vpnsessions', 'bytes_down', existing_type=sa.Integer(), type_=sa.BigInteger()) 20 | 21 | 22 | def downgrade(): 23 | op.alter_column('vpnsessions', 'bytes_up', existing_type=sa.BigInteger(), type_=sa.Integer()) 24 | op.alter_column('vpnsessions', 'bytes_down', existing_type=sa.BigInteger(), type_=sa.Integer()) 25 | 26 | -------------------------------------------------------------------------------- /alembic/versions/4396820d8f4_update_profile_table.py: -------------------------------------------------------------------------------- 1 | """update profile table 2 | 3 | Revision ID: 4396820d8f4 4 | Revises: 2e6851ff437 5 | Create Date: 2014-11-02 02:18:49.875105 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '4396820d8f4' 11 | down_revision = '2e6851ff437' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | client_os_enum = sa.Enum('windows', 'android', 'ubuntu', 'osx', 'freebox', 'other', name='client_os_enum') 19 | client_os_enum.create(op.get_bind(), checkfirst=False) 20 | protocols_enum = sa.Enum('udp', 'tcp', 'udpl', name='protocols_enum') 21 | protocols_enum.create(op.get_bind(), checkfirst=False) 22 | op.add_column('profiles', sa.Column('client_os', client_os_enum, nullable=True)) 23 | op.add_column('profiles', sa.Column('protocol', protocols_enum, nullable=False, server_default='udp')) 24 | op.add_column('profiles', sa.Column('disable_ipv6', sa.Boolean(), nullable=False, server_default='false')) 25 | op.add_column('profiles', sa.Column('gateway_country', sa.String(), nullable=True, server_default=None)) 26 | op.add_column('profiles', sa.Column('gateway_id', sa.Integer(), nullable=True, server_default=None)) 27 | op.add_column('profiles', sa.Column('use_http_proxy', sa.String(), nullable=True, server_default=None)) 28 | 29 | 30 | def downgrade(): 31 | op.drop_column('profiles', 'use_http_proxy') 32 | op.drop_column('profiles', 'gateway_id') 33 | op.drop_column('profiles', 'gateway_country') 34 | op.drop_column('profiles', 'protocol') 35 | op.drop_column('profiles', 'disable_ipv6') 36 | op.drop_column('profiles', 'client_os') 37 | sa.Enum(name='client_os_enum').drop(op.get_bind(), checkfirst=False) 38 | sa.Enum(name='protocols_enum').drop(op.get_bind(), checkfirst=False) 39 | 40 | -------------------------------------------------------------------------------- /alembic/versions/44db46b919f_add_pwresettoken.py: -------------------------------------------------------------------------------- 1 | """add pwresettoken 2 | 3 | Revision ID: 44db46b919f 4 | Revises: 1deecd313c4 5 | Create Date: 2014-01-12 00:26:21.920085 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '44db46b919f' 11 | down_revision = '1deecd313c4' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.create_table('pwresettoken', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('uid', sa.Integer(), nullable=False), 22 | sa.Column('token', sa.String(length=32), nullable=False), 23 | sa.Column('expire_date', sa.DateTime(), nullable=True), 24 | sa.ForeignKeyConstraint(['uid'], ['users.id'], ), 25 | sa.PrimaryKeyConstraint('id') 26 | ) 27 | ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('pwresettoken') 33 | ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /alembic/versions/516b5a4829e_add_tickets_table.py: -------------------------------------------------------------------------------- 1 | """Add tickets table 2 | 3 | Revision ID: 516b5a4829e 4 | Revises: 31cde2cf5e6 5 | Create Date: 2014-12-05 11:13:14.203925 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '516b5a4829e' 11 | down_revision = '31cde2cf5e6' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.create_table('tickets', 20 | sa.Column('id', sa.Integer(), nullable=False), 21 | sa.Column('user_id', sa.Integer(), nullable=False), 22 | sa.Column('create_date', sa.DateTime(), nullable=False), 23 | sa.Column('closed', sa.Boolean(), nullable=False), 24 | sa.Column('close_date', sa.DateTime(), nullable=True), 25 | sa.Column('subject', sa.String(), nullable=False), 26 | sa.Column('notify_owner', sa.Boolean(), nullable=False), 27 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 28 | sa.PrimaryKeyConstraint('id') 29 | ) 30 | op.create_table('ticket_messages', 31 | sa.Column('id', sa.Integer(), nullable=False), 32 | sa.Column('ticket_id', sa.Integer(), nullable=False), 33 | sa.Column('user_id', sa.Integer(), nullable=False), 34 | sa.Column('create_date', sa.DateTime(), nullable=False), 35 | sa.Column('content', sa.Text(), nullable=False), 36 | sa.ForeignKeyConstraint(['ticket_id'], ['tickets.id'], ), 37 | sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), 38 | sa.PrimaryKeyConstraint('id') 39 | ) 40 | ### end Alembic commands ### 41 | 42 | 43 | def downgrade(): 44 | ### commands auto generated by Alembic - please adjust! ### 45 | op.drop_table('ticket_messages') 46 | op.drop_table('tickets') 47 | ### end Alembic commands ### 48 | -------------------------------------------------------------------------------- /alembic/versions/956b1aac1_add_giftcodes_free_o.py: -------------------------------------------------------------------------------- 1 | """add giftcodes.free_only 2 | 3 | Revision ID: 956b1aac1 4 | Revises: a8195fe5a5 5 | Create Date: 2014-01-28 20:02:54.815149 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = '956b1aac1' 11 | down_revision = 'a8195fe5a5' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.add_column('giftcodes', sa.Column('free_only', sa.Boolean(), server_default='true', nullable=False)) 20 | ### end Alembic commands ### 21 | 22 | 23 | def downgrade(): 24 | ### commands auto generated by Alembic - please adjust! ### 25 | op.drop_column('giftcodes', 'free_only') 26 | ### end Alembic commands ### 27 | -------------------------------------------------------------------------------- /alembic/versions/a8195fe5a5_add_referrer.py: -------------------------------------------------------------------------------- 1 | """add referrer 2 | 3 | Revision ID: a8195fe5a5 4 | Revises: 44db46b919f 5 | Create Date: 2014-01-23 21:40:21.766716 6 | 7 | """ 8 | 9 | # revision identifiers, used by Alembic. 10 | revision = 'a8195fe5a5' 11 | down_revision = '44db46b919f' 12 | 13 | from alembic import op 14 | import sqlalchemy as sa 15 | 16 | 17 | def upgrade(): 18 | ### commands auto generated by Alembic - please adjust! ### 19 | op.add_column('users', sa.Column('referrer_id', sa.Integer(), nullable=True)) 20 | ### end Alembic commands ### 21 | 22 | 23 | def downgrade(): 24 | ### commands auto generated by Alembic - please adjust! ### 25 | op.drop_column('users', 'referrer_id') 26 | ### end Alembic commands ### 27 | -------------------------------------------------------------------------------- /ccvpn/filters.py: -------------------------------------------------------------------------------- 1 | 2 | def check(input): 3 | ''' Return unicode checkbox for a boolean ''' 4 | if input == 'True': 5 | return '☑' # 'BALLOT BOX WITH CHECK' 6 | else: 7 | return '☒' # 'BALLOT BOX WITH X' 8 | 9 | -------------------------------------------------------------------------------- /ccvpn/methods.py: -------------------------------------------------------------------------------- 1 | from .models import Order, PaypalAPI 2 | from pyramid.httpexceptions import HTTPOk, HTTPSeeOther, HTTPBadRequest, HTTPServerError 3 | import bitcoinrpc 4 | import logging 5 | import stripe 6 | import stripe.error 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | class Method(object): 11 | """ 12 | Payement Method interface. 13 | 14 | __init__() is called on app start. 15 | 16 | Order creation: 17 | - init() is called to create an Order object 18 | - Order objects is saved to database 19 | - start() is called to get the next step 20 | """ 21 | 22 | def __init__(self, settings): 23 | pass 24 | 25 | def init(self, user, time): 26 | raise NotImplementedError() 27 | 28 | def start(self, request, order): 29 | raise NotImplementedError() 30 | 31 | def view(self, request, order): 32 | raise NotImplementedError() 33 | 34 | def callback(self, request, order): 35 | raise NotImplementedError() 36 | 37 | 38 | class PaypalMethod(Method): 39 | name = 'paypal' 40 | id = Order.METHOD.PAYPAL 41 | 42 | def __init__(self, settings): 43 | default_addr = 'paypal@ccrypto.org' 44 | self.api = PaypalAPI() 45 | self.api.test = bool(settings.get('paypal.test', False)) 46 | self.api.header_image = settings.get('paypal.header_image', False) 47 | self.api.title = settings.get('paypal.title', 'CCrypto VPN') 48 | self.api.currency = settings.get('paypal.currency', 'EUR') 49 | self.api.address = settings.get('paypal.address', default_addr) 50 | self.api.receiver = settings.get('paypal.receiver', default_addr) 51 | self.month_price = float(settings.get('paypal.month_price', 2)) 52 | 53 | def init(self, user, time): 54 | amount = round(self.month_price * (time.days / 30), 2) 55 | o = Order(user=user, time=time, amount=amount, method=self.id) 56 | return o 57 | 58 | def start(self, request, order): 59 | link = self.api.make_link(order, request) 60 | return HTTPSeeOther(location=link) 61 | 62 | def callback(self, request, order): 63 | if self.api.handle_notify(order, request): 64 | return HTTPOk() 65 | else: 66 | return HTTPBadRequest() 67 | 68 | 69 | class StripeMethod(Method): 70 | name = 'stripe' 71 | id = Order.METHOD.STRIPE 72 | 73 | def __init__(self, settings): 74 | stripe.api_key = settings.get('stripe.api_key') 75 | self.pkey = settings.get('stripe.pkey') 76 | self.month_price = float(settings.get('stripe.month_price', 2.00)) 77 | 78 | def init(self, user, time): 79 | amount = self.month_price * (time.days / 30) 80 | o = Order(user=user, time=time, amount=amount, method=self.id) 81 | return o 82 | 83 | def start(self, request, order): 84 | loc = request.route_url('order_view', hexid=hex(order.id)[2:]) 85 | return HTTPSeeOther(location=loc) 86 | 87 | def view(self, request, order): 88 | months = int(order.time.days / 30) 89 | username = order.user.username 90 | return { 91 | 'pkey': self.pkey, 92 | 'amount': int(order.amount * 100), 93 | 'name': 'CCrypto VPN', 94 | 'description': '%d months for %s' % (months, username), 95 | 'currency': 'eur', 96 | } 97 | 98 | def callback(self, request, order): 99 | token = request.POST.get('stripeToken') 100 | if not token: 101 | return HTTPBadRequest('missing stripeToken') 102 | months = int(order.time.days / 30) 103 | username = order.user.username 104 | try: 105 | charge = stripe.Charge.create( 106 | amount=int(order.amount * 100), 107 | currency='eur', 108 | card=token, 109 | description='%d months for %s' % (months, username), 110 | ) 111 | order.payment['charge_id'] = charge['id'] 112 | if charge['refunded'] or not charge['paid']: 113 | request.message.error('Error: The order has not been payed or ' 114 | 'has been refunded') 115 | loc = request.route_url('order_view', hexid=hex(order.id)[2:]) 116 | return HTTPSeeOther(location=loc) 117 | order.paid_amount = float(charge['amount']) / 100.0 118 | order.paid = True 119 | order.user.add_paid_time(order.time) 120 | loc = request.route_url('order_view', hexid=hex(order.id)[2:]) 121 | return HTTPSeeOther(location=loc) 122 | except stripe.error.CardError as e: 123 | message = e.json_body['error']['message'] 124 | request.message.error('Card error: ' + message) 125 | order.payment['error'] = message 126 | loc = request.route_url('order_view', hexid=hex(order.id)[2:]) 127 | return HTTPSeeOther(location=loc) 128 | except stripe.error.AuthenticationError: 129 | log.critical('Authentication with Stripe failed') 130 | return HTTPServerError() 131 | except stripe.error.StripeError as e: 132 | log.critical('Stripe error: '+str(e)) 133 | return HTTPServerError() 134 | 135 | 136 | class BitcoinMethod(Method): 137 | name = 'bitcoin' 138 | id = Order.METHOD.BITCOIN 139 | 140 | def __init__(self, settings): 141 | user = settings.get('bitcoin.user', '') 142 | password = settings.get('bitcoin.password', '') 143 | host = settings.get('bitcoin.host', 'localhost') 144 | port = settings.get('bitcoin.port', 8332) 145 | if user == '': 146 | self.rpc = bitcoinrpc.connect_to_local() 147 | else: 148 | self.rpc = bitcoinrpc.connect_to_remote(user, password, host, port) 149 | self.month_price = float(settings.get('bitcoin.month_price', 0.02)) 150 | self.account = settings.get('bitcoin.account', 'ccvpn2') 151 | 152 | def init(self, user, time): 153 | amount = round(self.month_price * (time.days / 30), 4) 154 | o = Order(user=user, time=time, amount=amount, method=self.id) 155 | o.payment['btc_address'] = self.rpc.getnewaddress(self.account) 156 | return o 157 | 158 | def start(self, request, order): 159 | loc = request.route_url('order_view', hexid=hex(order.id)[2:]) 160 | return HTTPSeeOther(location=loc) 161 | 162 | def check_paid(self, order): 163 | if 'btc_address' not in order.payment: 164 | return False 165 | addr = order.payment['btc_address'] 166 | order.paid_amount = float(self.rpc.getreceivedbyaddress(addr)) 167 | if order.paid_amount >= order.amount: 168 | order.paid = True 169 | order.user.add_paid_time(order.time) 170 | 171 | -------------------------------------------------------------------------------- /ccvpn/models/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from .base import Base, DBSession 4 | 5 | from .sql import User, get_user, PasswordResetToken, Profile 6 | from .sql import GiftCode, AlreadyUsedGiftCode 7 | from .sql import Order, OrderNotPaid, Gateway, VPNSession 8 | from .sql import Ticket, TicketMessage 9 | from .sql import random_access_token, random_gift_code, random_bytes 10 | 11 | from .icinga import IcingaQuery, IcingaError 12 | 13 | from .paypal import PaypalAPI 14 | 15 | -------------------------------------------------------------------------------- /ccvpn/models/base.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import declarative_base 2 | from sqlalchemy.orm import scoped_session, sessionmaker 3 | from zope.sqlalchemy import ZopeTransactionExtension 4 | import logging 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | ext = ZopeTransactionExtension(keep_session=False) 9 | DBSession = scoped_session(sessionmaker(extension=ext)) 10 | 11 | 12 | class Base(object): 13 | @classmethod 14 | def one(cls, **kwargs): 15 | return DBSession.query(cls).filter_by(**kwargs).one() 16 | 17 | @classmethod 18 | def all(cls, **kwargs): 19 | return DBSession.query(cls).filter_by(**kwargs).all() 20 | 21 | 22 | Base = declarative_base(cls=Base) 23 | 24 | -------------------------------------------------------------------------------- /ccvpn/models/icinga.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | import requests 4 | import socket 5 | from beaker import cache 6 | 7 | 8 | class IcingaError(Exception): 9 | pass 10 | 11 | 12 | class IcingaQuery(object): 13 | def __init__(self, urlbase, auth): 14 | self.baseurl = urlbase 15 | self.auth = auth 16 | 17 | try: 18 | content = self._get_availcgi() 19 | data = json.loads(content) 20 | self.report = data['avail']['service_availability']['services'] 21 | except (requests.RequestException, socket.timeout) as e: 22 | raise IcingaError('failed to connect: ' + str(e)) 23 | except (ValueError, KeyError) as e: 24 | raise IcingaError('failed to decode icinga response (%s)' % str(e)) 25 | 26 | @cache.cache_region('short_term') 27 | def _get_availcgi(self): 28 | hostservices = 'all^all' 29 | timeperiod = 'last31days' 30 | backtrack = 4 31 | url = '/avail.cgi?hostservice=%s&timeperiod=%s&backtrack=%d&jsonoutput' 32 | url = url % (hostservices, timeperiod, backtrack) 33 | r = requests.get(self.baseurl + url, 34 | auth=self.auth, verify=False, timeout=2) 35 | content = r.content.decode('utf-8') 36 | 37 | # When querying services availability, icinga can break the JSON 38 | content = re.sub(',\n*(\]|\})', '\\1', content) 39 | 40 | return content 41 | 42 | def get_uptime(self, host, service): 43 | try: 44 | def fn(h): 45 | return h['host_name'] == host and \ 46 | h['service_description'] == service 47 | svcdata = next(filter(fn, self.report)) 48 | uptime = int(svcdata['percent_known_time_ok']) 49 | except (KeyError, ValueError) as e: 50 | raise IcingaError('failed to parse icinga report (%s)' % str(e), 51 | host, service) 52 | except StopIteration: 53 | raise IcingaError('host/service unknown to icinga', host, service) 54 | return uptime 55 | 56 | -------------------------------------------------------------------------------- /ccvpn/models/paypal.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlencode 2 | from urllib.request import urlopen 3 | 4 | 5 | class PaypalAPI(object): 6 | api_base = '' 7 | test = False 8 | header_image = False 9 | title = 'CCrypto VPN' 10 | currency = 'EUR' 11 | address = 'paypal@ccrypto.org' 12 | receiver = 'paypal@ccrypto.org' 13 | 14 | def __init__(self, test=False): 15 | self.test = test 16 | if test: 17 | self.api_base = 'https://www.sandbox.paypal.com/' 18 | else: 19 | self.api_base = 'https://www.paypal.com/' 20 | 21 | def make_link(self, order, request): 22 | baseurl = self.api_base + '/cgi-bin/webscr?' 23 | hexid = hex(order.id)[2:] 24 | params = { 25 | 'cmd': '_xclick', 26 | 'notify_url': request.route_url('order_callback', hexid=hexid), 27 | 'item_name': self.title, 28 | 'amount': order.amount, 29 | 'currency_code': self.currency, 30 | 'business': self.address, 31 | 'no_shipping': '1', 32 | 'return': request.route_url('order_view', hexid=hexid), 33 | 'cancel_return': request.route_url('order_view', hexid=hexid), 34 | } 35 | if self.header_image: 36 | params['cpp_header_image'] = self.header_image 37 | url = baseurl + urlencode(params) 38 | return url 39 | 40 | def handle_notify(self, order, request): 41 | # validate notify 42 | v_url = self.api_base + '/cgi-bin/webscr?cmd=_notify-validate' 43 | v_req = urlopen(v_url, data=bytes(request.body)) 44 | v_res = v_req.read() 45 | if v_res != b'VERIFIED': 46 | return False 47 | 48 | try: 49 | params = request.POST 50 | 51 | if 'test_ipn' in params: 52 | assert self.test and params['test_ipn'] == '1', \ 53 | 'Test API notify' 54 | 55 | if params['payment_status'] == 'Refunded': 56 | # Refund 57 | if type(order.payment) is not dict or len(order.payment) == 0: 58 | order.payment = { 59 | 'payerid': params['payer_id'], 60 | 'payeremail': params['payer_email'], 61 | } 62 | order.payment['status'] = 'refunded' 63 | order.paid = False 64 | # FIXME: maybe remove some time 65 | return True 66 | elif params['payment_status'] == 'Completed': 67 | assert self.receiver == params['receiver_email'], \ 68 | 'Wrong receiver: ' + params['receiver_email'] 69 | assert self.currency == params['mc_currency'], \ 70 | 'Wrong currency: ' + params['mc_currency'] 71 | assert params['txn_type'] == 'web_accept', \ 72 | 'Wrong transaction type: ' + params['txn_type'] 73 | 74 | order.paid_amount = float(params['mc_gross']) 75 | assert order.paid_amount >= order.amount, \ 76 | 'HAX! Paid %f, ordered %f.' % ( 77 | order.paid_amount, order.amount) 78 | 79 | # Store some data about the order 80 | order.payment = { 81 | 'txnid': params['txn_id'], 82 | 'payerid': params['payer_id'], 83 | 'payeremail': params['payer_email'], 84 | } 85 | order.paid = True 86 | order.user.add_paid_time(order.time) 87 | return True 88 | else: 89 | # Not implemented, ignore it 90 | print('received: ', params) 91 | return True 92 | 93 | except KeyError as ke: 94 | # Invalid notification - do not reply to it. 95 | print('invalid notification: ' + str(ke)) 96 | return False 97 | except AssertionError as error: 98 | # Well-formed notification with bad input. 99 | # We dont want to receive it again 100 | order.payment = dict(error=str(error)) 101 | print('Error: ' + str(error)) 102 | return True 103 | -------------------------------------------------------------------------------- /ccvpn/models/types.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import TypeDecorator, UnicodeText, String 2 | from sqlalchemy.ext.mutable import Mutable 3 | from sqlalchemy.dialects import postgresql # INET 4 | import json 5 | 6 | 7 | class JSONEncodedDict(TypeDecorator): 8 | impl = UnicodeText 9 | 10 | def process_bind_param(self, value, dialect): 11 | if value: 12 | return json.dumps(value) 13 | else: 14 | return None 15 | 16 | def process_result_value(self, value, dialect): 17 | if value is not None: 18 | return json.loads(value) 19 | else: 20 | return dict() 21 | 22 | 23 | class MutableDict(Mutable, dict): 24 | @classmethod 25 | def coerce(cls, key, value): 26 | "Convert plain dictionaries to MutableDict." 27 | 28 | if not isinstance(value, MutableDict): 29 | if isinstance(value, dict): 30 | return MutableDict(value) 31 | 32 | # this call will raise ValueError 33 | return Mutable.coerce(key, value) 34 | else: 35 | return value 36 | 37 | def __setitem__(self, key, value): 38 | "Detect dictionary set events and emit change events." 39 | 40 | dict.__setitem__(self, key, value) 41 | self.changed() 42 | 43 | def __delitem__(self, key): 44 | "Detect dictionary del events and emit change events." 45 | 46 | dict.__delitem__(self, key) 47 | self.changed() 48 | 49 | 50 | class INETWrapper(TypeDecorator): 51 | impl = String 52 | 53 | def load_dialect_impl(self, dialect): 54 | if dialect.name == 'postgresql': 55 | return dialect.type_descriptor(postgresql.INET()) 56 | else: 57 | return dialect.type_descriptor(String()) 58 | 59 | -------------------------------------------------------------------------------- /ccvpn/pages/api.md: -------------------------------------------------------------------------------- 1 | Title: Public API 2 | 3 | ## Gateway object: 4 | 5 | - **hostname**: *string*, the gateway's hostname. 6 | - **fqdn**: *string*, its fully qualified domain name. 7 | - **country**: *string*, ISO 3166-1 alpha-2 country codes. (fr, en, nl, ...) 8 | - **bandwidth**: *integer*, The bandwidth in bits/second. 9 | - **ipv4 and ipv6**: *string*, IP addresses of the gateway. Either can be null. 10 | - **enabled**: *boolean*, true if the gateway is enabled. 11 | 12 | Example: 13 | 14 | {"hostname": "fr-poney0", 15 | "fqdn": "fr-poney0.204vpn.net", 16 | "country": "fr", 17 | "bandwidth": 1000000000, 18 | "ipv4": "195.154.79.147", 19 | "ipv6": "2001:bc8:3505:1000::1", 20 | "enabled": true} 21 | 22 | ## Get a list of Gateway objects: 23 | 24 | Parameters: 25 | 26 | - **show_disabled**: *boolean*, should it include disabled gateways? 27 | (default: false) 28 | - **country**: *string*, filter by country code. 29 | - **hostname**: *string*, filter by hostname. 30 | 31 | Python example: 32 | 33 | #!/usr/bin/python 34 | 35 | """ 36 | This example prints the name of every enabled gateway 37 | """ 38 | 39 | import json 40 | import requests 41 | 42 | # GET /api/public/gateways 43 | r = requests.get('http://vpn.ccrypto.org/api/public/gateways') 44 | gateways = r.json() 45 | for gateway in gateways: 46 | print(gateway['hostname']) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ccvpn/pages/faq.fr.md: -------------------------------------------------------------------------------- 1 | Title: Questions fréquemment posées 2 | 3 | [TOC] 4 | 5 | Géneral 6 | ------- 7 | 8 | ### Qu'est-ce qu'un VPN ? 9 | Un VPN (Réseau Privé Virtuel) est un réseau virtual permettant de considérer 10 | plusieurs ordinateurs distants comme étant sur le même réseau local. 11 | Ici, c'est utilisé pour faire passer tout le traffic de nos clients 12 | à travers nos serveurs. 13 | Cela permet de paraître anonyme sur Internet et de chiffrer vos communications 14 | pour qu'un intermédiaire (FAI, Wifi public, ...) ne puisse pas vous espionner, 15 | et vous garanti un accès neutre au réseau. 16 | 17 | ### Pourquoi payer pour un VPN au lieu d'utiliser Tor ? 18 | Tor a les avantages d'être gratuit et parfaitement anonyme, mais les noeuds 19 | de sortie ne sont pas toujours dignes de confiance, et c'est beaucoup plus 20 | lent qu'un VPN. 21 | Un VPN est donc largement plus adapté à une utilisation de tous les jours. 22 | 23 | ### Avez-vous un programme d'affiliation ? 24 | Oui, vous pouvez partager un lien associé à votre compte, qui vous 25 | fera gagner 2 semaines de VPN pour chaque client l'ayant suivi. 26 | Inviter 24 personnes vous donne donc 1 an de VPN gratuit ! 27 | 28 | ### Est-ce que le VPN supporte l'IPv6 ? 29 | Oui ! 30 | 31 | ### Puis-je avoir une adresse statique ? 32 | Non, pas pour le moment. 33 | 34 | ### Allez vous supporter le PPTP ? 35 | Non, pour des raisons de sécurité. 36 | 37 | ### Y a-t-il une limite de bande passante ? 38 | Non, tous les utilisateurs partagent équitablement la connexion des serveurs. 39 | Nous faisons en sorte qu'il y ait toujours un minimum de 20Mbps disponible 40 | pour chaque client. 41 | 42 | ### Censurez-vous certains sites ou protocoles ? 43 | Non, et nous ne le ferons jamais. Le VPN vous fourni un accès complêtement neutre. 44 | 45 | ### Avec quels protocoles fonctionne le VPN ? 46 | Notre VPN est fait avec OpenVPN. 47 | 48 | ### Quelles méthodes de payement sont disponibles ? 49 | Vous pouvez payer par Paypal ou Stripe (carte), ou encore avec des Bitcoins. 50 | Vous pouvez [nous contacter](/page/help) si vous avez besoin d'un autre moyen 51 | de payement. 52 | 53 | ### Est-ce Libre ? 54 | Oui ! Notre VPN fonctionne avec OpenVPN, et ce site ansi que les outils que nous 55 | avons développé pour gérer le VPN sont libres et disponibles sur 56 | [GitHub](https://github.com/CCrypto/). 57 | 58 | ### Pourquoi y a-t-il une erreur SSL ? 59 | Nous utilisons un certificat signé par CACert, une autorité de certification 60 | à but non lucratif. 61 | Elle est souvent installée sur les distributions GNU/Linux, mais vous pouvez 62 | l'installer facilement, [comme expliqué sur leur wiki](http://wiki.cacert.org/FAQ/BrowserClients). 63 | 64 | ### Est-ce vraiment sécurisé ? 65 | Oui, le VPN utilise différents algorithmes de chiffrement fiables et nous ne 66 | gardons aucune données sensible sur les serveurs du VPN. 67 | Les comptes clients et historiques de connexions sont uniquement gardés sur des 68 | serveurs séparés. 69 | 70 | ### Y aura-t-il plus de serveurs ou dans d'autres pays ? 71 | Oui, nous ajoutons des serveurs en fonction de la demande et de nos moyens. 72 | Si vous voudriez héberger un serveur, recommander un bon hébergeur, ou 73 | seriez simplement intéressé par une certain pays, contactez nous. 74 | 75 | Comptes 76 | ------- 77 | 78 | ### Puis-je avoir un compte de test gratuit ? 79 | Oui, pendant 7 jours. 80 | Vous n'avez qu'à [créer un compte](/account/signup) et [nous contacter](/page/help). 81 | 82 | ### Puis-je utiliser mon compte sur plusieurs machines ? 83 | Oui, vous pouvez utiliser votre compte avec un maximum de 10 connexions 84 | simultannées. 85 | Vous devrez cependant créer un profile pour chacune des 86 | connexions. 87 | 88 | ### Comment supprimer mon compte ? 89 | [Contactez nous](/page/help). 90 | 91 | 92 | Légal 93 | ----- 94 | 95 | ### Quelles informations gardez-vous ? 96 | Nous conservons uniquement l'adresses IP et l'heure de chaque connexion, 97 | comme exigé par la loi. Nous n'analysons et n'enregistrons rien concernant 98 | les données passant par le VPN. 99 | 100 | ### Est-ce réellement anonyme ? 101 | Ça dépend de votre définition d'anonyme. 102 | C'est anonyme, parce que nous ne vous demandons pas votre nom et ne vérifions pas 103 | votre identité pour vous laisser profiter du VPN, et que nous autorisons des 104 | méthodes de payement anonymes, comme le Bitcoin. 105 | C'est anonyme, parce que l'on ne peut pas associer une connexion à travers le VPN 106 | à votre vraie adresse. 107 | Mais, les autorités françaises peuvent nous demander votre historique de 108 | connexions et les données associées à votre compte. (nom, adresse e-mail, ...) 109 | 110 | ### Donnez vous des informations aux autorités ? 111 | Seulement si requis par la loi. 112 | Dans ce cas, nous essaierons de contacter le ou les clients concernés avant tout. 113 | 114 | 115 | -------------------------------------------------------------------------------- /ccvpn/pages/faq.md: -------------------------------------------------------------------------------- 1 | Title: Frequently Asked Questions 2 | 3 | [TOC] 4 | 5 | General 6 | ------- 7 | 8 | ### What is a VPN? 9 | A Virtual Private Network is a private network on the Internet. 10 | Here, it is made of our customers and our servers acts like routers. 11 | That means that everything goes through our servers, and you appear anonymous 12 | on the Internet. 13 | Because of the strong encryption used, your ISP cannot log or 14 | filter anything. 15 | 16 | ### Why should I pay to use it instead of tor? 17 | Although tor may be free (and extremely good for some usage), tor is also very 18 | slow. 19 | This means that tor is good if you want full anonymity, but not for 20 | everyday web browsing, or to play an online game. For that, a VPN is perfect. 21 | 22 | ### Do you propose an affiliate program? 23 | Yes! Share your affiliate link and earn 2 weeks for each referral. 24 | Invite 24 friends and you get one year of free VPN! 25 | 26 | ### Do you support IPv6? 27 | Yes! 28 | 29 | ### Can I have a static IP address? 30 | Not at the moment. 31 | 32 | ### Will you support PPTP? 33 | No, for security reasons. 34 | 35 | ### Do you monitor or limit bandwidth usage? 36 | No, every user share each VPN server's connection. 37 | We always try to have enough bandwidth (at least 20Mbps) available 38 | for everyone. 39 | 40 | ### Do you censor some websites or protocols? 41 | No and we will never do it. 42 | 43 | ### Which protocols are supported? 44 | We only support OpenVPN for now. 45 | 46 | ### Which payment methods are supported? 47 | We support Paypal and Bitcoin. 48 | Feel free to ask [the support](/page/help) if you need any other method. 49 | 50 | ### Is it open-source? 51 | Yes! Our VPN is made with OpenVPN. 52 | Our servers' config and this website are also open-source and available on our 53 | [GitHub project](https://github.com/CCrypto/ccvpn/). 54 | 55 | ### Why is there a SSL error? 56 | We are using a SSL certificate signed by CACert. 57 | It is a non-profit, community-driven certificate authority. 58 | While it is commonly found on GNU/Linux distributions, you may need to trust 59 | CACert [as explained on their wiki](http://wiki.cacert.org/FAQ/BrowserClients). 60 | 61 | ### Are my data kept secure? 62 | Yes, the VPN traffic is strongly encrypted and we do not keep any data on the 63 | VPN servers. 64 | The website and database are on a different server, in a 65 | different datacenter. 66 | 67 | ### Will there be more servers/countries available? 68 | Yes, but we first need money to pay the servers. 69 | If you would like to have a server somewhere, know a good provider or would 70 | like to host one, please contact us. 71 | 72 | ### How many kitten power this service? 73 | One. We called it Jack. 74 | 75 | Account 76 | ------- 77 | 78 | ### Can I have a trial account? 79 | Yes, we provide 7 days trial accounts. 80 | You just have to [sign up](/account/signup) and [contact us](/page/help). 81 | 82 | ### Can I use my account on multiple machines? 83 | Yes, you can! Up to 10 times! 84 | You have to create a profile for each machine to be able to use 85 | them at the same time. 86 | 87 | ### How can I delete my account? 88 | Contact [the support](/page/help). 89 | 90 | 91 | Legal 92 | ----- 93 | 94 | ### What do you log? 95 | We only keep VPN connection IP addresses because of the law. 96 | We do not keep any data concerning your traffic. 97 | 98 | ### Is it really anonymous? 99 | Depends of your definition of anonymous. 100 | It is anonymous, because we will not ask you for your name or verify your 101 | identity to be able to use the VPN, and we use anonymous payment methods 102 | like Bitcoin. 103 | It is anonymous, because no one can find out your identity from the other side 104 | of the VPN. 105 | However, French authorities can ask us for user data. That's why we 106 | recommend you to not use your usual username here so that your VPN account 107 | cannot be associated to anything you do with it. 108 | 109 | ### Will you log traffic or send user data to authorities? 110 | Only if required by the law to keep the service running. 111 | In this case, we'll try to contact you by email before doing anything. 112 | 113 | 114 | -------------------------------------------------------------------------------- /ccvpn/pages/help.fr.md: -------------------------------------------------------------------------------- 1 | Title: Aide 2 | 3 | -> [**Questions fréquemment posées**](/page/faq) 4 | 5 | Guides d'installation: 6 | 7 | - [pour Android](/page/install-android) 8 | - [pour Windows](/page/install-windows) 9 | - [pour GNU/Linux](/page/install-gnulinux) 10 | - [pour OSX](/page/install-osx) 11 | 12 | Support: 13 | 14 | - **[Auto-Diagnostic](/page/self-diagnosis)** : Avant de demander de l'aide, vérifiez si vous trouvez la solution à votre problème ici. 15 | - **[Tickets](/tickets/)** : Le moyen le plus efficace de nous contacter. 16 | - **IRC** : Allez sur #ccrypto sur chat.freenode.net avec votre client préféré 17 | ou [ce client web](https://kiwiirc.com/client/chat.freenode.net/?nick=ccvpn|${irc_username}&theme=cli#ccrypto). 18 | Mentionnez les ops et nous essayerons de vous répondre aussi vite que possible. 19 | - **E-Mail** : support at ccrypto.org 20 | 21 | 22 | Autres pages: 23 | 24 | - [API publique](/page/api) 25 | - [informations techniques](/page/tech) 26 | 27 | -------------------------------------------------------------------------------- /ccvpn/pages/help.md: -------------------------------------------------------------------------------- 1 | Title: Help 2 | 3 | -> [**Frequently Asked Questions**](/page/faq) 4 | 5 | Installation guides: 6 | 7 | - [for Android](/page/install-android) 8 | - [for Windows](/page/install-windows) 9 | - [for GNU/Linux](/page/install-gnulinux) 10 | - [for OSX](/page/install-osx) 11 | 12 | Support: 13 | 14 | - **[Self-Diagnosis](/page/self-diagnosis)**: Before asking for help, check here if you find the solution to your problem. 15 | - **[Tickets](/tickets/)**: The recommended and most reliable way to get help. 16 | - **IRC**: Join #ccrypto on chat.freenode.net with your favorite client or 17 | [our webchat](https://kiwiirc.com/client/chat.freenode.net/?nick=ccvpn|${irc_username}&theme=cli#ccrypto). 18 | Ask the channel operators and we will try to help you as soon as possible. 19 | - **E-Mail**: support at ccrypto.org 20 | 21 | Other: 22 | 23 | - [Public API](/page/api) 24 | - [tech stuff](/page/tech) 25 | 26 | -------------------------------------------------------------------------------- /ccvpn/pages/install-android.fr.md: -------------------------------------------------------------------------------- 1 | Title: Installation sur Android 2 | 3 | Nous recommandons l'utilisation de l'application 4 | [*OpenVPN for Android* de Arne Schwabe][openvpn_android], 5 | 6 | D'abord, téléchargez le fichier .ovpn dans [votre compte](/account/) 7 | et enregistrez-le. 8 | 9 | Ensuite, ouvrez *OpenVPN for Android* et importez le fichier téléchargé. 10 | Vous pouvez éditer la configuration dans l'application pour définir votre 11 | nom de compte et votre mot de passe, pour qu'ils ne vous soient pas demandé 12 | à la connexion. 13 | 14 | Vous pouvez maintenant vous connecter au VPN. 15 | 16 | Si vous avez une question, n'hésitez pas à [nous contacter](/page/help). 17 | 18 | [openvpn_android]: https://play.google.com/store/apps/details?id=de.blinkt.openvpn 19 | 20 | -------------------------------------------------------------------------------- /ccvpn/pages/install-android.md: -------------------------------------------------------------------------------- 1 | Title: Install on Android 2 | 3 | We recommend [Arne Schwabe's OpenVPN for Android][openvpn_android], 4 | as it is the only enough uptodate OpenVPN app at the moment. 5 | 6 | Download the .ovpn file you need in [your account](/account/) and save it 7 | somewhere on your Android device. 8 | 9 | Then, start OpenVPN and import the .ovpn file. 10 | Edit the imported profile and set your username and password if you do not 11 | want to be asked every time. 12 | 13 | You can now connect to that profile. 14 | 15 | If you have any questions, go to the [Support page](/page/help). 16 | 17 | [openvpn_android]: https://play.google.com/store/apps/details?id=de.blinkt.openvpn 18 | 19 | -------------------------------------------------------------------------------- /ccvpn/pages/install-gnulinux.fr.md: -------------------------------------------------------------------------------- 1 | Title: Installation sous GNU/Linux 2 | 3 | Vous aurez besoin d'un fichier : Dans [votre compte](/account/), téléchargez 4 | la config. Vous pouvez le renommer en ccrypto.conf. 5 | 6 | **N'utilisez pas le plugin OpenVPN pour Network-Manager.** 7 | N-M ne supporte pas certaines options récentes d'OpenVPN, et ne peut simplement 8 | pas se connecter à notre VPN. 9 | 10 | Si vous avez une question, n'hésitez pas à [nous contacter](/page/help). 11 | 12 | 13 | Fedora 16 ou plus récent 14 | ------------------------ 15 | **Vous devez être connecté en tant que root pour démarrer le VPN. 16 | Il est aussi possible d'utiliser sudo.** 17 | 18 | Installez OpenVPN : 19 | 20 | yum install openvpn 21 | 22 | Placez le fichier que vous avez téléchargé dans `/etc/openvpn/`. 23 | Par exemple : `/etc/openvpn/ccrypto.conf` 24 | 25 | cd /lib/systemd/system 26 | ln openvpn@.service openvpn@ccrypto.service 27 | 28 | Démarrez OpenVPN : 29 | 30 | systemctl start openvpn@ccrypto.service 31 | 32 | Maintenant, vous pouvez le faire démarrer en même temps que l'OS 33 | si vous le souhaitez : 34 | 35 | systemctl enable openvpn@ccrypto.service 36 | 37 | 38 | Debian/Ubuntu 39 | ------------- 40 | **Vous devez être connecté en tant que root pour démarrer le VPN. 41 | Il est aussi possible d'utiliser sudo.** 42 | 43 | Installez OpenVPN : 44 | 45 | apt-get install openvpn resolvconf 46 | 47 | Placez le fichier que vous avez téléchargé dans `/etc/openvpn/`. 48 | Par exemple : `/etc/openvpn/ccrypto.conf` 49 | 50 | Démarrez OpenVPN : 51 | 52 | service openvpn start ccrypto 53 | 54 | Pour le démarrer en même temps que l'OS, enregistrez vos identifiants comme 55 | expliqué dans la partie en dessous, et éditez `/etc/default/openvpn` pour 56 | décommenter et modifiez la ligne `AUTOSTART`: 57 | 58 | AUTOSTART="ccrypto" 59 | 60 | Linux Mint Mate Edition 17 ou plus récent 61 | ------------- 62 | 63 | Pré-requis: Mettez à jour votre système en utilisant la commande: 64 | 65 | ``` 66 | sudo aptitude update 67 | ``` 68 | 69 | Installez les différents programmes utilisés pour faire fonctionner votre VPN CCrypto: 70 | 71 | ``` 72 | sudo aptitude install openvpn resolvconf network-manager-openvpn-gnome 73 | ``` 74 | 75 | Redémarrez la machine pour finaliser l'installation. 76 | 77 | Il faut maintenant télécharger le fichier de configuration disponible [sur le site dans la partie account](https://vpn.ccrypto.org/account/). 78 | et le placer dans /etc/openvpn. Téléchargez le fichier ca.crt [disponible ici](https://vpn.ccrypto.org/ca.crt) et placez le dans /etc/openvpn. 79 | 80 | Il s'agit d'un fichier .ovpn (qui fonctionne tel que sous Windows). Un simple renommage de ce fichier en ccrypto-*.conf suffit a faire fonctionner le tout. 81 | 82 | - On clique gauche sur l'icône réseau du Tableau de bord > Connexions Réseaux > Ajouter 83 | - Sélectionnez "importez une configuration VPN enregistrée" 84 | - On va dans le répertoire /etc/openvpn/ et on sélectionne le fichier ccrypto-*.conf. 85 | La fenêtre suivante devrait s'afficher: ![screenshot](http://i.imgur.com/HcdRwgP.png) 86 | 87 | Choisissez Mot de passe comme type d'authentification. Rentrez votre nom d'utilisateur ainsi que votre mot de passe 88 | Et dans certificat du CA, sélectionnez votre ca.crt téléchargé précedemment. 89 | 90 | Votre VPN est prêt à l'utilisation. 91 | 92 | Enregistrer les identifiants 93 | ---------------------------- 94 | Vous pouvez faire qu'OpenVPN enregistre votre nom d'utilisateur et votre mot de 95 | passe, pour ne pas avoir à l'entrer à chaque connexion. 96 | 97 | Créez un fichier texte "ccrypto_creds.txt" contenant votre nom sur la 98 | première ligne, et votre mot de passe sur la deuxième, comme ceci: 99 | 100 | JackSparrow 101 | s0mep4ssw0rd 102 | 103 | Déplacez-le ensuite dans `/etc/openvpn/`, avec le fichier 104 | ccrypto.conf que vous avez téléchargé plus tôt. 105 | 106 | Ouvrez ccrypto.ovpn avec un éditeur de texte (vim, gedit, kate, ...) 107 | et ajouter une ligne à la fin: 108 | 109 | auth-user-pass /etc/openvpn/ccrypto_creds.txt 110 | 111 | Pour que seul root puisse lire ce fichier : 112 | 113 | chown root:root /etc/openvpn/ccrypto_creds.txt 114 | chmod 600 /etc/openvpn/ccrypto_creds.txt 115 | 116 | Autres distributions 117 | -------------------- 118 | 119 | Vous devriez lire un guide adapté à la distribution : 120 | 121 | * ArchLinux 122 | 123 | -------------------------------------------------------------------------------- /ccvpn/pages/install-gnulinux.md: -------------------------------------------------------------------------------- 1 | Title: Install on GNU/Linux 2 | 3 | You will need a config file : In [your account](/account/), download the 4 | .ovpn file. You can rename it to .conf. 5 | 6 | **Do not use Network-Manager. It is known to not work.** 7 | N-M ignores some of OpenVPN's config options, and simply cannot connect to 8 | our VPN. 9 | We are still waiting for this issue to be fixed. 10 | 11 | If you have any questions, go to the [Support page](/page/help). 12 | 13 | 14 | Fedora 16 or later 15 | ------------------ 16 | **You need to login as root for all these commands, or use sudo.** 17 | 18 | yum install openvpn 19 | 20 | Put the config file you have downloaded in `/etc/openvpn/`. 21 | ie: `/etc/openvpn/ccrypto.conf` 22 | 23 | cd /lib/systemd/system 24 | ln openvpn@.service openvpn@ccrypto.service 25 | 26 | Start it: 27 | 28 | systemctl start openvpn@ccrypto.service 29 | 30 | Now, you can make it start on boot. It should ask for your password on boot. 31 | You can start it on boot: 32 | 33 | systemctl enable openvpn@ccrypto.service 34 | 35 | 36 | Debian/Ubuntu 37 | ------------- 38 | **You need to login as root for all these commands, or use sudo.** 39 | 40 | Download and install OpenVPN: 41 | 42 | apt-get install openvpn resolvconf 43 | 44 | Put the config file you have downloaded in `/etc/openvpn/`. 45 | ie: `/etc/openvpn/ccrypto.conf` 46 | 47 | Now, you can start it : 48 | 49 | service openvpn start ccrypto 50 | 51 | To start it on boot, [save your creditentials](/page/auth-user-pass) first, and 52 | edit `/etc/default/openvpn` and uncomment/change the `AUTOSTART` line: 53 | 54 | AUTOSTART="ccrypto" 55 | 56 | 57 | Linux Mint 17 Mate Edition or later 58 | ------------- 59 | 60 | First, you have to update your system by using the following command: 61 | 62 | ``` 63 | sudo aptitude update 64 | ``` 65 | 66 | Download and install OpenVPN and Network-manager-openvpn-gnome: 67 | 68 | ``` 69 | sudo aptitude install openvpn resolvconf network-manager-openvpn-gnome 70 | ``` 71 | 72 | Reboot your system . 73 | 74 | Save the config file [you've downloaded from your ccrypto account section](https://vpn.ccrypto.org/account/) into /etc/openvpn/. 75 | Rename the file to eg. ccrypto-*.conf if necessary. 76 | 77 | [Download the ca.crt file](https://vpn.ccrypto.org/ca.crt) and put it in /etc/opvpn aswell. 78 | 79 | - Left click on the Network icon in the Control Panel> Network Connections> Add 80 | - Select "Import a saved VPN configuration" 81 | - Select your ccrypto-*.conf config file from the /etc.openvpn directory 82 | 83 | Select "password authentication" as the authentication type and enter your ccrypto username and password. 84 | Select the ca.crt you saved into /etc/openvpn and click "Save". 85 | 86 | Your VPN is now ready to use. 87 | 88 | Save username and password 89 | -------------------------- 90 | You can make OpenVPN remember your username and password, so you don't need 91 | to type them everytime you want to use the VPN. 92 | 93 | This can be done by creating a text file named "ccrypto_creds.txt" containing 94 | your username on the first line and your password on the second 95 | (see example below). 96 | Put it in /etc/openvpn/, along with the config file. 97 | 98 | It should look like this: 99 | 100 | JackSparrow 101 | s0mep4ssw0rd 102 | 103 | Then, open the .ovpn or .conf file with a text editor (vim, gedit, kate, ...) 104 | and add this line at the end of the file: 105 | 106 | auth-user-pass /etc/openvpn/ccrypto_creds.txt 107 | 108 | Now, if you restart OpenVPN, it should not ask you for your password anymore. 109 | 110 | You can make sure only root will be able to access this file: 111 | 112 | chown root:root /etc/openvpn/ccrypto_creds.txt 113 | chmod 600 /etc/openvpn/ccrypto_creds.txt 114 | 115 | 116 | Other 117 | ----- 118 | 119 | You should check for a distribution-specific install howto : 120 | 121 | * ArchLinux 122 | 123 | -------------------------------------------------------------------------------- /ccvpn/pages/install-osx.fr.md: -------------------------------------------------------------------------------- 1 | Title: Installation sous Mac OS X 2 | 3 | Télécharger la configuration dans [votre compte](/account/) et placez-la dans 4 | `~/Library/Application Support/Tunnelblick/Configurations/`. 5 | 6 | Vous pouvez maintenant l'utiliser avec Tunnelblick comme [documenté ici](https://code.google.com/p/tunnelblick/wiki/UsingTunnelblick#Normal_Tunnelblick_Operation). 7 | 8 | Si vous avez une question, n'hésitez pas à [nous contacter](/page/help). 9 | 10 | -------------------------------------------------------------------------------- /ccvpn/pages/install-osx.md: -------------------------------------------------------------------------------- 1 | Title: Install on Mac OS X 2 | 3 | Download the .ovpn file you need in [your account](/account/) and save it 4 | in `~/Library/Application Support/Tunnelblick/Configurations/`. 5 | 6 | You can now use it as [documented here](https://code.google.com/p/tunnelblick/wiki/UsingTunnelblick#Normal_Tunnelblick_Operation). 7 | 8 | If you have any questions, go to the [Support page](/page/help). 9 | 10 | -------------------------------------------------------------------------------- /ccvpn/pages/install-windows.fr.md: -------------------------------------------------------------------------------- 1 | Title: Installation sous Windows 2 | 3 | Tout d'abord, il vous faut télécharger et installer OpenVPN, disponible ici: 4 | [OpenVPN.net](http://openvpn.net/index.php/open-source/downloads.html) 5 | Récupérez le `Windows Installer` et lancez-le. 6 | 7 | 8 | Dans [votre compte](/account/), téléchargez le fichier .ovpn que vous voulez 9 | et placez-le dans `C:\Program Files\OpenVPN\config\`. 10 | 11 | Pour commencer à utiliser le VPN, démarrez `OpenVPN GUI` **en tant qu'administrateur**. 12 | Dans la zone de notification, vous devriez voir une icône OpenVPN : 13 | Faites un clic droit dessus, et cliquez sur `Connect`. 14 | 15 | 16 | Enregistrer les identifiants 17 | ---------------------------- 18 | Vous pouvez faire qu'OpenVPN enregistre votre nom d'utilisateur et votre mot de 19 | passe, pour ne pas avoir à l'entrer à chaque connexion. 20 | 21 | Créez un fichier texte "ccrypto_creds.txt" contenant votre nom sur la 22 | première ligne, et votre mot de passe sur la deuxième, comme ceci: 23 | 24 | JackSparrow 25 | s0mep4ssw0rd 26 | 27 | Déplacez-le ensuite dans `C:\Program Files\OpenVPN\config\`, avec le fichier 28 | ccrypto.ovpn que vous avez téléchargé plus tôt. 29 | 30 | Ouvrez ccrypto.ovpn avec un éditeur de texte (Bloc-notes, Notepad++, ...) 31 | et ajouter une ligne à la fin: 32 | 33 | auth-user-pass ccrypto_creds.txt 34 | 35 | Pour finir, redémarrez OpenVPN GUI et connectez vous : il ne devrait plus vous 36 | demander votre mot de passe. 37 | 38 | 39 | 40 | Résolution de problèmes 41 | ----------------------- 42 | 43 | Tout d'abord, assurez vous d'avoir bien démarré OpenVPN en tant qu'administrateur 44 | et que ccrypto.ovpn est correctement placé dans `C:\Program Files\OpenVPN\config\`. 45 | 46 | 47 | ### netsh.exe 48 | 49 | Si vous trouvez ces lignes dans votre historique OpenVPN: 50 | 51 | NETSH: C:\Windows\system32\netsh.exe interface ipv6 set address Connexion au réseau local 52 | ERROR: netsh command failed: returned error code 1 53 | 54 | Cette erreur est fréquente sous windows et semble arriver à cause d'un problème 55 | d'OpenVPN avec netsh.exe et l'IPv6. 56 | Pour le résoudre, renommez votre connection réseau pour éviter les espaces. 57 | Par exemple « Connexion au réseau local » en « lan ». 58 | 59 | - [(fr) Renommer une connexion réseau](http://windows.microsoft.com/fr-xf/windows-vista/rename-a-network-connection) 60 | 61 | 62 | ### Multiples interfaces TAP 63 | Error: When using --tun-ipv6, if you have more than one TAP-Windows adapter, you must also specify --dev-node 64 | Exiting due to fatal error 65 | 66 | Cette erreur pourra apparaitre si vous avec de multiples interfaces TAP, 67 | la plupart du temps à cause d'un autre logiciel utilisant TAP. 68 | Pour le résoudre, ouvrez un interpréteur de commandes (Shift + Clic droit) 69 | dans votre répertoire OpenVPN (là où openvpn.exe se situe) et lancez : 70 | 71 | openvpn.exe --show-adapters 72 | 73 | Cela va lister vos interfaces TAP. 74 | Puis, ouvrez votre fichier de configuration ccrypto.ovpn avec un éditeur de texte 75 | et ajoutez ceci sur une nouvelle ligne : 76 | 77 | dev-node [nom] 78 | 79 | Remplacez [nom] par le nom de votre interface TAP. 80 | 81 | 82 | ### Ça ne fonctionne toujours pas ? 83 | 84 | Si vous ne pouvez toujours pas utiliser le VPN, n'hésitez pas à 85 | [nous contacter](/page/help). 86 | Joignez les logs d'OpenVPN à votre message, pour nous aider à trouver 87 | le problème au plus vite. 88 | 89 | 90 | -------------------------------------------------------------------------------- /ccvpn/pages/install-windows.md: -------------------------------------------------------------------------------- 1 | Title: Install on Windows 2 | 3 | First, you need to download OpenVPN: 4 | [OpenVPN.net](http://openvpn.net/index.php/open-source/downloads.html) 5 | Get the `Windows Installer` and install OpenVPN. 6 | 7 | In [your account](/account/), download the config file (.ovpn) you want to use, 8 | and copy it to `C:\Program Files\OpenVPN\config\`. 9 | 10 | To start using it, run `OpenVPN GUI` you just installed **as Administrator**. 11 | In the Windows System Tray, you should see a OpenVPN icon; 12 | right click on it, and click on `Connect`. 13 | 14 | 15 | 16 | Save username and password 17 | -------------------------- 18 | You can make OpenVPN remember your username and password, so you don't need 19 | to type them everytime you want to use the VPN. 20 | 21 | This can be done by creating a text file named "ccrypto_creds.txt" containing 22 | your username on the first line and your password on the second 23 | (see example below). 24 | Move it to `C:\Program Files\OpenVPN\config\`, next to the .ovpn file you 25 | copied there before. 26 | 27 | It should look like this: 28 | 29 | JackSparrow 30 | s0mep4ssw0rd 31 | 32 | Then, open the .ovpn file with a text editor (Notepad, Notepad++, ...) 33 | and add this line at the end of the file: 34 | 35 | auth-user-pass ccrypto_creds.txt 36 | 37 | Now, if you restart OpenVPN, it should not ask you for your password anymore. 38 | 39 | 40 | 41 | Troubleshooting 42 | --------------- 43 | 44 | First, make sure you have started OpenVPN as Administrator and that your 45 | ccrypto.ovpn file exists in `C:\Program Files\OpenVPN\config\`. 46 | 47 | 48 | ### netsh.exe error 49 | 50 | If you find lines like those in your OpenVPN log: 51 | 52 | NETSH: C:\Windows\system32\netsh.exe interface ipv6 set address Local Area Network 53 | ERROR: netsh command failed: returned error code 1 54 | 55 | This error is really frequent on Windows and seem to happen because of 56 | a OpenVPN problem with netsh.exe and IPv6. 57 | To fix it, rename your network connection to avoid spaces, 58 | for example "Local Area Network" to "lan". 59 | 60 | - [Rename a network connection](http://windows.microsoft.com/en-au/windows-vista/rename-a-network-connection) 61 | 62 | 63 | ### Multiple TAP-Windows adapters 64 | 65 | Error: When using --tun-ipv6, if you have more than one TAP-Windows adapter, you must also specify --dev-node 66 | Exiting due to fatal error 67 | 68 | That one can happen when you have multiple TAP-Windows adapters, most of the 69 | time because of another software using TAP. 70 | 71 | To fix it, open a command prompt (Shift+Right click) in your OpenVPN directory 72 | (where openvpn.exe is), and run: 73 | 74 | openvpn.exe --show-adapters 75 | 76 | This will list your TAP adapters. 77 | Then, open your ccrypto.ovpn configuration file with notepad and add this on a 78 | new line: 79 | 80 | dev-node [name] 81 | 82 | Replace [name] by your TAP adapter name. 83 | 84 | 85 | ### Still doesn't work 86 | 87 | If you still cannot use the VPN, please go to the [Support page](/page/help) 88 | and we'll do our best to help you. 89 | Please also send us your OpenVPN logs. 90 | 91 | 92 | -------------------------------------------------------------------------------- /ccvpn/pages/self-diagnosis.fr.md: -------------------------------------------------------------------------------- 1 | Title: Auto-Diagnostic 2 | 3 | J'ai un fichier ".ovpn" mais il me faut un ".conf" ! 4 | -------- 5 | Il vous suffit de changer l'extension en renommant le fichier. 6 | 7 | Il m'est impossible d'utiliser votre VPN avec Network-Manager. 8 | -------- 9 | Tout d'abord, vérifiez que vous avez correctement créé le profil (tutoriel à venir). 10 | Si c'est bien le cas, avant toute chose, vérifiez qu'OpenVPN lui-même est opérationnel en utilisant cette commande : 11 | `sudo openvpn --config ccrypto.conf` 12 | (assurez-vous de remplacer "ccrypto.conf" par le nom de votre fichier de configuration) 13 | 14 | Je suis connecté mais je ne peux pas ping google.com o_o 15 | -------- 16 | Essayez de `ping 8.8.8.8`, si ça marche, votre ordinateur n'utilise pas le serveur DNS. Ajoutez `nameserver 10.99.0.20` au début de /etc/resolv.con **une fois la connexion établie**. Sinon, lisez la suite. 17 | 18 | Ça ne marche toujours pas ! 19 | -------- 20 | En utilisant la commande `ip r` (abbrégé pour `ip route`), vérifiez que vous avez, entre autre choses, les lignes suivantes : 21 | > `0.0.0.0/1 via 10.99.2.1 dev tun0` 22 | > `10.99.0.0/24 via 10.99.2.1 dev tun0` 23 | > `10.99.2.0/24 dev tun0 proto kernel scope link src 10.99.2.18` 24 | > `128.0.0.0/1 via 10.99.2.1 dev tun0` 25 | > `199.115.114.65 via 192.168.1.1 dev wlan0` 26 | Ces valeurs peuvent (et pour certaines, vont) changer suivant votre configuration (par exemple : wlan0 → eth0, 192.168.1.1 → 192.168.42.23, etc.) 27 | Si vous n'avez pas toutes ces lignes, relancez OpenVPN ou ajouter les routes à la main en utilisant `ip r a` (`ip route add`). Si vous ne savez pas comment fair, ce serait mieux de venir nous demander sur IRC (nous allons avoir besoin des sorties des commandes `ip a` (`ip address`) et `ip r`, veuillez utiliser https://paste.cubox.me et nous envoyer uniquement le lien vers le paste). 28 | 29 | J'ai tout essayé mais rien ne semble fonctionner ! T_T 30 | --------- 31 | Ok… Je pense que vous pouvez venir [nous demander sur IRC](https://kiwiirc.com/client/chat.freenode.net/?nick=ccvpn|?&theme=cli#ccrypto) (mais souvenez-vous que nous ne sommes pas des professionnels payés, nous ne sommes pas toujours présent mais nous finirons toujours par répondre si vous ne partez pas trop vite). 32 | 33 | -------------------------------------------------------------------------------- /ccvpn/pages/self-diagnosis.md: -------------------------------------------------------------------------------- 1 | Title: Self Diagnosis 2 | 3 | I have a ".ovpn" file but I need a ".conf"! 4 | -------- 5 | You just have to change the suffix by renamming the file. 6 | 7 | I'm unable to use your VPN with Network-Manager. 8 | -------- 9 | First, check that you have properly created the profile (tutorial to come). 10 | If it's the case, before anything else, let's make sure that OpenVPN itself is working with the following command: 11 | `sudo openvpn --config ccrypto.conf` 12 | (make sure to replace "ccrypto.conf" by the actual name of your configuration file) 13 | 14 | I'm connected but cannot ping google.com o_o 15 | -------- 16 | Try to `ping 8.8.8.8`, if it works then your computer doesn't use the right DNS server. Add `nameserver 10.99.0.20` at the beginning of /etc/resolv.conf **once the connection is made**. Else, continue reading. 17 | 18 | It still doesn't work! 19 | -------- 20 | Using the `ip r` (short for `ip route`) command, make sure you have, alongside with other lines, the following: 21 | > `0.0.0.0/1 via 10.99.2.1 dev tun0` 22 | > `10.99.0.0/24 via 10.99.2.1 dev tun0` 23 | > `10.99.2.0/24 dev tun0 proto kernel scope link src 10.99.2.18` 24 | > `128.0.0.0/1 via 10.99.2.1 dev tun0` 25 | > `199.115.114.65 via 192.168.1.1 dev wlan0` 26 | These values might (and for some, will) change a little depending on your configuration (for example: wlan0 → eth0, 192.168.1.1 → 192.168.42.23, etc.). 27 | If you don't have every one of these lines, kill OpenVPN and fire it again or add the routes by hand using `ip r a` (`ip route add`). If you don't know how to do it, it would be best to come ask on IRC (we will need the output of both `ip a` (`ip address`) and `ip r`, please paste them into https://paste.cubox.me and just give us the link to the paste). 28 | 29 | I've tried everything but nothing seems to work! T_T 30 | --------- 31 | Ok… I guess now you can come [ask us on IRC](https://kiwiirc.com/client/chat.freenode.net/?nick=ccvpn|?&theme=cli#ccrypto) (but remember to stay a while, we're not payed professionnal, we might not be around at a given time but we will answer later on). 32 | 33 | -------------------------------------------------------------------------------- /ccvpn/pages/tech.md: -------------------------------------------------------------------------------- 1 | Title: Tech Stuff 2 | 3 | Software 4 | -------- 5 | We use OpenVPN on GNU/Linux, with tun. 6 | Every port is allowed, on TCP an UDP, with IPv4 and IPv6. 7 | 8 | You can connect to OpenVPN servers with: 9 | 10 | - UDP port 1194 (default). Recommended. 11 | - TCP port 443 (HTTPS). It should pass through most of the firewalls, but it is slower and less reliable than the first. 12 | 13 | Our OpenVPN config files automatically tries to use UDP, then TCP. 14 | 15 | Security 16 | -------- 17 | We have a 4096b RSA key for authentication and key exchange, 18 | and allow the best ciphers available to provide *perfect forward secrecy* 19 | to up-to-date clients. 20 | It took 4 hours to generate the Diffie-Hellman parameters. 21 | 22 | Client certificate authentication 23 | --------------------------------- 24 | We allow you to authenticate with a certificate instead of a password. 25 | This is not available in the user panel through ; 26 | you will need to send us a certificate signing request by email to 27 | the support and we will send you back the signed certificate. 28 | 29 | IPv4 ports forwarding 30 | --------------------- 31 | Because of the cost of IPv4 adresses, 32 | you share your IPv4 address behind a NAT router, 33 | but you can ask to have a port range forwarded. 34 | 35 | IPv6 36 | ---- 37 | All of our gateways support IPv6. 38 | Each connected client is provided his own a IPv6 address 39 | with all ports open for TCP and UDP. 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ccvpn/pages/tos.md: -------------------------------------------------------------------------------- 1 | Title: Terms of Service 2 | 3 | Cognitive Cryptography provides virtual private networking (VPN) to its clients. You (Client) 4 | acknowledge that Cognitive Cryptography and/or any of its parent companies or constituents 5 | will not be liable for any and all liability arising from your use of its services 6 | and website. 7 | 8 | Services are provided "as is" with no express or implied warranty for quality 9 | and speed. While Cognitive Cryptography attempt to maintain the services available, it may 10 | be subject to inavailabilities for various including maintenances, failures and 11 | third-party services failure. 12 | 13 | By using our services and/or website, you agree in full to the terms and 14 | conditions provided herein, in addition to all future amendments and modifications. 15 | If you do not agree to these terms and conditions, then access to the service is prohibed. 16 | 17 | Clients who breaches or violates our terms and conditions may have their 18 | account and access to the service removed withour any refund. 19 | 20 | ### Acceptable Use 21 | We support your right to free speech and privacy, but you need to comply with 22 | all applicable laws and regulations in France and the country in which you 23 | reside to use this service. 24 | 25 | By using our services, you agree to not engage in any of the following activities: 26 | 27 | - SPAM. You agree to not send or facilitate any unsolicited email. 28 | - Anything that could be considered hostile by a third party, including but 29 | not limited to Denial-of-Service attacks. 30 | - Receiving or distributing any illegal content 31 | - Receiving or distributing any copyrighted, trademark, or patented content 32 | which you do not own or lack consent or license from the owner. 33 | - Any action that results in inclusion on IP blacklists. 34 | 35 | ### Client Responsabilities 36 | As a client, you are responsible for maintaining the confidentility and 37 | security of the account you are provided. 38 | 39 | ### Cognitive Cryptography's Rights 40 | Cognitive Cryptography reserves the right to close your account and access to the service 41 | at any given time without any given notice. While Cognitive Cryptography attempts to provide 42 | complete and quality service to its clients, this right is reserved for reasons 43 | which may arise at a later date. 44 | 45 | Client understands that Cognitive Cryptography for reasons beyond its control may shut down 46 | and terminate services. In this case, clients will be notified at least thirty 47 | (30) days in advance, and will not be eligible for a partial or complete refund. 48 | 49 | ### Refund Policy 50 | If you are not 100% satisfied with your purchase, within 7 days from the 51 | purchase date, we will fully refund the cost of your order. 52 | 53 | ### Privacy Policy 54 | 55 | What do we collect? 56 | 57 | - Your account settings. 58 | - Payment data, including transaction IDs and Paypal or Bitcoin adresses. 59 | - The IP adresses used to log in. 60 | 61 | How is my Information used? 62 | Your information is used to provide you and maintain the service. 63 | We do not sell or redistribute your information. 64 | Those informations will never leave our servers unless we are required to by 65 | the law. If this happens, we will inform all people concerned. 66 | 67 | We use cookies. 68 | 69 | -------------------------------------------------------------------------------- /ccvpn/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # package 2 | -------------------------------------------------------------------------------- /ccvpn/scripts/apiacl.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from argparse import ArgumentParser, RawTextHelpFormatter 3 | from sqlalchemy import engine_from_config 4 | from pyramid.paster import get_appsettings, setup_logging 5 | 6 | from ccvpn.models import DBSession, Gateway 7 | import logging 8 | log = logging.getLogger(__name__) 9 | 10 | 11 | def add(args): 12 | t = Gateway() 13 | t.label = args.label 14 | if args.token == '-': 15 | args.token = input('Token (empty=random): ') 16 | if args.token: 17 | t.token = args.token 18 | if args.remote_addr: 19 | t.remote_addr = args.remote_addr 20 | DBSession.add(t) 21 | DBSession.commit() 22 | print('Inserted. token=%s' % t.token) 23 | 24 | 25 | def revoke(args): 26 | q = DBSession.query(Gateway) 27 | if args.token == '-': 28 | args.token = input('Search token (empty=*): ') 29 | if args.token: 30 | q = q.filter_by(token=args.token) 31 | if args.label: 32 | q = q.filter_by(label=args.label) 33 | count = q.count() 34 | if count == 0: 35 | print('No token found.') 36 | return 37 | if count > 1: 38 | if args.force: 39 | print('Warning: mutliple tokens match.') 40 | else: 41 | print('Error: mutliple tokens match. Use -f to force.') 42 | return 43 | sure_str = 'Sure revoking %d tokens? [y/n] ' % q.count() 44 | if not args.yes and input(sure_str).lower() != 'y': 45 | return 46 | for t in q.all(): 47 | print('Revoking token #%d (%s)...' % (t.id, t.label)) 48 | DBSession.delete(t) 49 | 50 | 51 | def main(argv=sys.argv): 52 | parser = ArgumentParser(description=__doc__, 53 | formatter_class=RawTextHelpFormatter) 54 | parser.add_argument('-v', '--verbose', action='count', 55 | help='Increase verbosity') 56 | parser.add_argument('config') 57 | 58 | subparsers = parser.add_subparsers(title='subcommands') 59 | parser_add = subparsers.add_parser('add', help='add API access token') 60 | parser_add.set_defaults(func=add) 61 | parser_add.add_argument('label') 62 | parser_add.add_argument('-t', '--token', default='') 63 | parser_add.add_argument('-r', '--remote-addr', default='') 64 | 65 | parser_rev = subparsers.add_parser('rev', help='revoke API access token') 66 | parser_rev.set_defaults(func=revoke) 67 | parser_rev.add_argument('-n', '--label', default='') 68 | parser_rev.add_argument('-t', '--token', default='') 69 | parser_rev.add_argument('-r', '--remote-addr', default='') 70 | parser_rev.add_argument('-y', '--yes', default=False, action='store_true', 71 | help='Dont ask for confirmation') 72 | parser_rev.add_argument('-f', '--force', default=False, 73 | action='store_true', 74 | help='Revoke even if multiple found') 75 | 76 | args = parser.parse_args() 77 | 78 | log_level = logging.WARNING 79 | if args.verbose is not None: 80 | verbose = int(args.verbose) 81 | if verbose == 1: 82 | log_level = logging.INFO 83 | elif verbose >= 2: 84 | log_level = logging.DEBUG 85 | logging.basicConfig(level=log_level) 86 | 87 | config_uri = args.config 88 | setup_logging(config_uri) 89 | settings = get_appsettings(config_uri) 90 | engine = engine_from_config(settings, 'sqlalchemy.') 91 | DBSession.configure(bind=engine) 92 | 93 | args.func(args) 94 | 95 | -------------------------------------------------------------------------------- /ccvpn/scripts/checkbtcorders.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from sqlalchemy import engine_from_config 5 | from pyramid.paster import get_appsettings, setup_logging 6 | import transaction 7 | 8 | from ccvpn.models import DBSession, Order 9 | from ccvpn.methods import BitcoinMethod 10 | 11 | import logging 12 | log = logging.getLogger(__name__) 13 | 14 | 15 | def usage(argv, out=sys.stdout): 16 | cmd = os.path.basename(argv[0]) 17 | out.write('usage: %s \n' 18 | '(example: "%s development.ini")\n' % (cmd, cmd)) 19 | sys.exit(1) 20 | 21 | 22 | def checkbtcorders(settings): 23 | method = BitcoinMethod(settings) 24 | 25 | orders = DBSession.query(Order) \ 26 | .filter_by(paid=False, method=Order.METHOD.BITCOIN) 27 | for order in orders: 28 | method.check_paid(order) 29 | log.debug('Order#%d: amount=%f, paid=%f', order.id, order.amount, 30 | order.paid_amount) 31 | 32 | 33 | def main(argv=sys.argv): 34 | if len(argv) != 2: 35 | usage(argv) 36 | config_uri = argv[1] 37 | setup_logging(config_uri) 38 | settings = get_appsettings(config_uri) 39 | engine = engine_from_config(settings, 'sqlalchemy.') 40 | DBSession.configure(bind=engine) 41 | with transaction.manager: 42 | checkbtcorders(settings) 43 | 44 | -------------------------------------------------------------------------------- /ccvpn/scripts/expire_mail.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script sends an email to accounts that are going to expire or has expired. 3 | """ 4 | 5 | import sys 6 | from argparse import ArgumentParser, RawTextHelpFormatter 7 | from datetime import datetime, timedelta 8 | 9 | from sqlalchemy import engine_from_config 10 | from sqlalchemy.sql import or_ 11 | from sqlalchemy.sql import func 12 | from pyramid.config import Configurator 13 | from pyramid.paster import get_appsettings, setup_logging 14 | from pyramid_mailer import mailer_factory_from_settings 15 | from pyramid_mailer.message import Message 16 | from pyramid.renderers import render 17 | import transaction 18 | 19 | from ccvpn.models import DBSession, User 20 | 21 | import logging 22 | log = logging.getLogger(__name__) 23 | 24 | 25 | def send_notice(user, mailer): 26 | body = render('mail/expiry.mako', { 27 | 'user': user, 28 | }) 29 | 30 | message = Message(subject='CCVPN: Account expiration', 31 | recipients=[user.email], 32 | body=body) 33 | mailer.send_immediately(message) 34 | 35 | user.last_expiry_notice = datetime.now() 36 | 37 | 38 | def get_future_expire(days=3): 39 | """This function get user accounts that will expire in a few days. 40 | """ 41 | 42 | limit_date = datetime.now() + timedelta(days=days) 43 | 44 | q = DBSession.query(User) \ 45 | .filter(User.email != '') \ 46 | .filter(User.email != None) 47 | 48 | # Expire now < expiration < N days 49 | q = q.filter(User.paid_until > datetime.now()) 50 | q = q.filter(User.paid_until < limit_date) 51 | 52 | # Only send notice if the last one was before the first time we could have 53 | # sent a notice 54 | # [last notice] < [expiration - 3days] < [this notice] < [expiration] 55 | if DBSession.bind.dialect.name == 'sqlite': 56 | a1 = func.julianday(User.paid_until) 57 | a2 = func.julianday(User.last_expiry_notice) 58 | cond = a1 - a2 > days 59 | else: 60 | cond = User.paid_until - User.last_expiry_notice > timedelta(days=days) 61 | 62 | q = q.filter(or_(User.last_expiry_notice == None, cond)) 63 | 64 | users = list(q.all()) 65 | 66 | log.debug('found %d accounts that expire in less than %d days', 67 | len(users), days) 68 | return users 69 | 70 | 71 | def get_expired(): 72 | """This function get expired user accounts. 73 | """ 74 | 75 | q = DBSession.query(User) \ 76 | .filter(User.email != '') \ 77 | .filter(User.email != None) 78 | 79 | # Is expired 80 | q = q.filter(User.paid_until < datetime.now()) 81 | 82 | # Only send notice if the last notice was before expiration 83 | q = q.filter(or_(User.last_expiry_notice == None, 84 | User.last_expiry_notice < User.paid_until)) 85 | 86 | users = list(q.all()) 87 | 88 | log.debug('found %d expired accounts.', len(users)) 89 | 90 | return users 91 | 92 | 93 | def main(argv=sys.argv): 94 | parser = ArgumentParser(description=__doc__, 95 | formatter_class=RawTextHelpFormatter) 96 | parser.add_argument('-v', '--verbose', action='count') 97 | parser.add_argument('-s', '--send', action='store_true', default=False) 98 | parser.add_argument('config') 99 | 100 | args = parser.parse_args() 101 | 102 | log_level = logging.WARNING 103 | if args.verbose is not None: 104 | verbose = int(args.verbose) 105 | if verbose == 1: 106 | log_level = logging.INFO 107 | elif verbose >= 2: 108 | log_level = logging.DEBUG 109 | logging.basicConfig(level=log_level) 110 | 111 | config_uri = args.config 112 | setup_logging(config_uri) 113 | settings = get_appsettings(config_uri) 114 | engine = engine_from_config(settings, 'sqlalchemy.') 115 | DBSession.configure(bind=engine) 116 | 117 | if 'mako.directories' not in settings: 118 | settings['mako.directories'] = 'ccvpn:templates/' 119 | if 'mako.imports' not in settings: 120 | settings['mako.imports'] = 'from ccvpn.filters import check' 121 | config = Configurator(settings=settings) 122 | config.include('pyramid_mako') 123 | config.commit() 124 | config.begin() 125 | 126 | mailer = mailer_factory_from_settings(settings) 127 | 128 | try: 129 | days = int(settings.get('expire_mail_days', 3)) 130 | except ValueError: 131 | log.warning('Invalid integer value for expire_mail_days. Using default') 132 | days = 3 133 | 134 | users = get_future_expire(days) + get_expired() 135 | 136 | if args.send: 137 | for u in users: 138 | print('sending notice to %s (%s)' % (u.username, u.email)) 139 | send_notice(u, mailer) 140 | transaction.commit() 141 | else: 142 | for u in users: 143 | print('not sending notice to %s (%s)' % (u.username, u.email)) 144 | print('Use -s to send messages') 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /ccvpn/scripts/initializedb.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from sqlalchemy import engine_from_config 5 | from pyramid.paster import get_appsettings, setup_logging 6 | import transaction 7 | 8 | from ccvpn.models import DBSession, Base, User 9 | 10 | 11 | def usage(argv, out=sys.stdout): 12 | cmd = os.path.basename(argv[0]) 13 | out.write('usage: %s \n' 14 | '(example: "%s development.ini")\n' % (cmd, cmd)) 15 | sys.exit(1) 16 | 17 | 18 | def initialize_db(): 19 | Base.metadata.create_all(DBSession.bind.engine) 20 | if not DBSession.query(User).filter_by(username='admin').count(): 21 | with transaction.manager: 22 | admin = User(username='admin', is_admin=True) 23 | admin.set_password('admin') 24 | DBSession.add(admin) 25 | 26 | 27 | def main(argv=sys.argv): 28 | if len(argv) != 2: 29 | usage(argv) 30 | config_uri = argv[1] 31 | setup_logging(config_uri) 32 | settings = get_appsettings(config_uri) 33 | engine = engine_from_config(settings, 'sqlalchemy.') 34 | DBSession.configure(bind=engine) 35 | initialize_db() 36 | 37 | -------------------------------------------------------------------------------- /ccvpn/scripts/mail.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from argparse import ArgumentParser, RawTextHelpFormatter 3 | from sqlalchemy import engine_from_config 4 | from pyramid.paster import get_appsettings, setup_logging 5 | from pyramid_mailer import mailer_factory_from_settings 6 | from pyramid_mailer.message import Message 7 | from ccvpn.models import DBSession, User 8 | 9 | import logging 10 | log = logging.getLogger(__name__) 11 | 12 | def main(argv=sys.argv): 13 | parser = ArgumentParser(description=__doc__, 14 | formatter_class=RawTextHelpFormatter) 15 | parser.add_argument('-v', '--verbose', action='count') 16 | parser.add_argument('-S', '--send', action='store_true', default=False) 17 | parser.add_argument('--active', action='store_true', default=True) 18 | parser.add_argument('-s', '--subject', action='store') 19 | parser.add_argument('config') 20 | parser.add_argument('textfile') 21 | 22 | args = parser.parse_args() 23 | 24 | log_level = logging.WARNING 25 | if args.verbose is not None: 26 | verbose = int(args.verbose) 27 | if verbose == 1: 28 | log_level = logging.INFO 29 | elif verbose >= 2: 30 | log_level = logging.DEBUG 31 | logging.basicConfig(level=log_level) 32 | 33 | config_uri = args.config 34 | setup_logging(config_uri) 35 | settings = get_appsettings(config_uri) 36 | engine = engine_from_config(settings, 'sqlalchemy.') 37 | DBSession.configure(bind=engine) 38 | 39 | mailer = mailer_factory_from_settings(settings) 40 | 41 | if not args.subject: 42 | print('Require a subject.') 43 | exit(1) 44 | 45 | message_in = open(args.textfile, 'r').read() 46 | if not message_in: 47 | print('Require a message.') 48 | exit(1) 49 | 50 | q = DBSession.query(User) 51 | q = q.filter(User.email != '') 52 | q = q.filter(User.email is not None) 53 | if args.active: 54 | q = q.filter_by(is_paid=True) 55 | users = list(q.all()) 56 | 57 | print('Sending to: %d users.' % (len(users))) 58 | if args.verbose: 59 | for u in users: 60 | print('- %s [%s]' % (u.email, u.username)) 61 | 62 | for u in users: 63 | if not args.send: 64 | print('Not sending message to %s.' % u.email) 65 | continue 66 | message = Message(subject=args.subject, 67 | recipients=[u.email], 68 | body=message_in) 69 | mailer.send_immediately(message) 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /ccvpn/static/7proxies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCrypto/ccvpn2/6bbfd01f41816bea905518f302f4cec474fdd221/ccvpn/static/7proxies.png -------------------------------------------------------------------------------- /ccvpn/static/OpenSans-Extrabold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCrypto/ccvpn2/6bbfd01f41816bea905518f302f4cec474fdd221/ccvpn/static/OpenSans-Extrabold.woff -------------------------------------------------------------------------------- /ccvpn/static/OpenSans.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCrypto/ccvpn2/6bbfd01f41816bea905518f302f4cec474fdd221/ccvpn/static/OpenSans.woff -------------------------------------------------------------------------------- /ccvpn/static/anon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 46 | 48 | 52 | 56 | 60 | 64 | 65 | -------------------------------------------------------------------------------- /ccvpn/static/bg-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /ccvpn/static/bg-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCrypto/ccvpn2/6bbfd01f41816bea905518f302f4cec474fdd221/ccvpn/static/bg-title.png -------------------------------------------------------------------------------- /ccvpn/static/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCrypto/ccvpn2/6bbfd01f41816bea905518f302f4cec474fdd221/ccvpn/static/bg.png -------------------------------------------------------------------------------- /ccvpn/static/bolt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 46 | 50 | 54 | -------------------------------------------------------------------------------- /ccvpn/static/cheap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 46 | 48 | 52 | 56 | 60 | 64 | 65 | -------------------------------------------------------------------------------- /ccvpn/static/fast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 46 | 50 | 54 | -------------------------------------------------------------------------------- /ccvpn/static/openvpn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 58 | 64 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /ccvpn/static/ping.js: -------------------------------------------------------------------------------- 1 | var npings = 6; 2 | 3 | function ping(host, callback, avg, count) { 4 | var start; 5 | var done = false; 6 | var img = new Image(); 7 | if (typeof(avg) === 'undefined') { 8 | avg = 0; 9 | } 10 | if (typeof(count) === 'undefined') { 11 | // -1 to ignore the first ping, so the DNS lookup does not change 12 | // the average time. 13 | count = -1; 14 | } 15 | count = count + 1; 16 | function ok() { 17 | done = true; 18 | var time = new Date() - start; 19 | if (count > 0) { 20 | avg = ((avg * (count-1)) + time) / (count) 21 | } else { 22 | avg = 0; 23 | } 24 | if (count >= npings) { 25 | callback(Math.round(avg) + 'ms'); 26 | } else { 27 | ping(host, callback, avg, count); 28 | } 29 | } 30 | function fail() { 31 | if (!done) { 32 | callback('timeout'); 33 | } 34 | } 35 | img.onload = ok; 36 | img.onerror = ok; 37 | start = new Date(); 38 | img.src = host; 39 | var timer = setTimeout(fail, 1500); 40 | callback('...'); 41 | } 42 | 43 | window.addEventListener('load', function() { 44 | var lines = document.getElementsByClassName('host_line'); 45 | for (var i=0; i 2 | 3 | 4 | image/svg+xml 46 | 48 | 52 | 56 | 57 | -------------------------------------------------------------------------------- /ccvpn/subscribers.py: -------------------------------------------------------------------------------- 1 | from pyramid.i18n import get_localizer, TranslationStringFactory 2 | 3 | def add_renderer_globals(event): 4 | request = event['request'] 5 | event['_'] = request.translate 6 | event['localizer'] = request.localizer 7 | 8 | tsf = TranslationStringFactory('ccvpn') 9 | 10 | def add_localizer(event): 11 | request = event.request 12 | localizer = get_localizer(request) 13 | def auto_translate(*args, **kwargs): 14 | return localizer.translate(tsf(*args, **kwargs)) 15 | request.localizer = localizer 16 | request.translate = auto_translate 17 | -------------------------------------------------------------------------------- /ccvpn/templates/account/index.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Account' %> 2 | <%inherit file="layout.mako" /> 3 | <% 4 | user = request.user 5 | profiles = request.user.profiles 6 | %> 7 |
8 |

${_('Account')} : ${user.username}

9 | 10 | % if user.is_paid: 11 |

${_('Your account is paid until ${until}. (${time} day(s) left)', 12 | mapping={'until': user.paid_until.strftime('%Y-%m-%d'), 13 | 'time': user.paid_days_left()})}

14 | % else: 15 |

${_('Your account is not paid.')}

16 | % if len(user.paid_orders) == 0: 17 |

${_('You can request a free 7 days trial here')}: 18 | ${_('Support')}

19 | % endif 20 |
21 | % endif 22 |
23 | 24 | 30 | 31 | 39 | 40 |
41 |
42 | 44 | 45 |
46 | 47 | 54 | 55 |
56 | 57 |

${_('Profiles')}

58 | 59 |

${_('help_connection_limit')}

60 | 61 | % if profiles: 62 | % for profile in profiles: 63 |
64 |

${_('VPN username')}: 65 | % if profile.name: 66 | ${request.user.username}/${profile.name} 67 | % else: 68 | ${request.user.username} (${_('default profile')}) 69 | % endif 70 |

71 |
    72 |
  • ${_('Gateway')}: 73 | % if profile.gateway_country: 74 | ${_('Only in country')} ${profile.gateway_country.upper()} 75 | % elif profile.gateway_id: 76 | ${_('Only')} ${profile.gateway} 77 | % else: 78 | ${_('Random')} 79 | % endif 80 | (${profile.PROTOCOLS[profile.protocol]}) 81 |
  • 82 |
  • ${_('OS')}: 83 | % if profile.client_os: 84 | ${profile.CLIENT_OS[profile.client_os]} 85 | % else: 86 | ${_('Not set')} 87 | % endif 88 | % if profile.disable_ipv6: 89 | [${_('IPv6 disabled')}] 90 | % endif 91 |
  • 92 | % if profile.use_http_proxy: 93 |
  • ${_('Use HTTP proxy')}: ${profile.use_http_proxy}
  • 94 | % endif 95 |
96 | 97 | <% 98 | rurl = request.route_url 99 | edit_url = rurl('account_profiles_edit', id=profile.id) 100 | del_url = rurl('account') 101 | if profile.name: 102 | dl_url = rurl('config_profile', 103 | username=profile.user.username, 104 | pname=profile.name) 105 | else: 106 | dl_url = rurl('config', 107 | username=profile.user.username) 108 | %> 109 | 110 | % if profile.name != '': 111 | 112 |
113 | 114 | 115 |
116 | % endif 117 | 118 | 119 |
120 | 121 |
122 | 123 | 124 | ${_('Download config file')} 125 | ### TODO: wget-able DL url 126 |
127 | %endfor 128 | % if len(profiles) < limit: 129 |

${_('Add Profile')}

130 |
131 |

132 | 134 | 135 |

136 |
137 | % endif 138 | % else: 139 |

${_('You have no profiles for now. Create one to start using the VPN')}:

140 |
141 |

142 | 144 | 145 |

146 |
147 | % endif 148 | 149 |
150 | 151 | -------------------------------------------------------------------------------- /ccvpn/templates/account/layout.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="../layout.mako" /> 2 | <% 3 | left_menu = [ 4 | ('/account/', _('Account')), 5 | ('/account/settings', _('Settings')), 6 | #('/account/profiles', 'Profiles'), 7 | None, 8 | ('/account/orders', _('Subscription')), 9 | ('/account/logs', _('VPN Logs')), 10 | ] 11 | %> 12 |
13 |
14 |

${_('Account')}

15 |
    16 | % for lmi in left_menu: 17 | % if lmi is None: 18 |
  • 19 | % else: 20 |
  • ${lmi[1]}
  • 21 | % endif 22 | % endfor 23 |
24 |
25 |
26 | ${next.body()} 27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /ccvpn/templates/account/logs.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Account Logs' %> 2 | <%inherit file="layout.mako" /> 3 |
4 |

${_('Logs')}

5 | 6 |

${_('Literally everything we need to keep about you.') | n}

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | % for line in logs: 20 | 21 | 22 | 23 | 24 | 25 | % if line.bytes_up and line.bytes_down: 26 | 27 | % else: 28 | 29 | % endif 30 | 31 | % endfor 32 | 33 |
${_('Date')}${_('Duration')}${_('Client IP')}${_('Shared IP')}${_('Bandwidth')}
${line.connect_date | n,date_fmt}${timedelta_fmt(line.duration) or _('Active') | n}${line.remote_addr or '['+_('unknown')+']'}${line.gateway.main_ip4 or '['+_('unknown')+']'}${line.bytes_up | n,bytes_fmt} / ${line.bytes_down | n,bytes_fmt}[${_('unknown')}]
34 | ${self.pager(page, pages)} 35 |
36 | -------------------------------------------------------------------------------- /ccvpn/templates/account/orders.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Account Orders' %> 2 | <%inherit file="layout.mako" /> 3 | 4 | <% 5 | def status_text(o): 6 | if o.paid: 7 | return _('Paid') 8 | 9 | text = _('Waiting') 10 | 11 | # It is possible to continue Bitcoin payments 12 | if order.method == order.METHOD.BITCOIN: 13 | url = request.route_url('order_view', hexid=hex(o.id)[2:]) 14 | text = '{}'.format(url, text) 15 | 16 | # It can also be partially paid 17 | if order.paid_amount > 0: 18 | ptext = _('paid ${amount}', mapping={ 19 | 'amount': str(order.paid_amount) + order.currency, 20 | }) 21 | text += ' (' + ptext + ')' 22 | 23 | return text 24 | %> 25 |
26 |

${_('Subscription')}

27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | % for order in orders: 38 | 39 | 40 | 43 | 44 | 45 | % endfor 46 | 47 |
${_('Date')}${_('Value')}${_('Status')}
${order.start_date | n,date_fmt}${order.time.days} ${_('days')} 41 | (${order.amount} ${order.currency}) 42 | ${status_text(order) | n}
48 | ${self.pager(page, pages)} 49 |
50 | -------------------------------------------------------------------------------- /ccvpn/templates/account/profiles_edit.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Account Profile' %> 2 | <%inherit file="layout.mako" /> 3 |
4 |

Profile: ${profile.name}

5 | 6 |
7 | % if profile.name: 8 | ## Only show name if it's not the default profile 9 | 10 | 11 | % endif 12 | 13 | 14 | 23 | 24 | 25 | 36 | 37 | 38 | 47 |

${_('help_tcp_usage')}

48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /ccvpn/templates/account/settings.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Account Settings' %> 2 | <%inherit file="layout.mako" /> 3 | <% user = request.user %> 4 |
5 |

${_('Settings')}

6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/add.mako: -------------------------------------------------------------------------------- 1 |

Add ${view_context.without_id().title}

2 | 3 |
4 | % for field in view_context.edit_fields: 5 | <% 6 | if field.editonly: 7 | continue 8 | %> 9 | 10 | 23 | % endfor 24 | 25 |
26 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/home.mako: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CCrypto/ccvpn2/6bbfd01f41816bea905518f302f4cec474fdd221/ccvpn/templates/admin/home.mako -------------------------------------------------------------------------------- /ccvpn/templates/admin/item.mako: -------------------------------------------------------------------------------- 1 | % if item_title: 2 |

${item_title}

3 | % endif 4 | 5 |
6 | % for field in view_context.edit_fields: 7 | <% 8 | value = getattr(item, field.attr) 9 | if value is None: 10 | value = '' 11 | %> 12 | 13 | 35 | % endfor 36 | % if view_context.can_edit: 37 | 38 | % endif 39 |
40 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/layout.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="../layout.mako" /> 2 |
3 |
4 |

Admin

5 |
    6 | % for lmi in left_menu: 7 | % if lmi is None: 8 |
  • 9 | % else: 10 |
  • ${lmi[1]}
  • 11 | % endif 12 | % endfor 13 |
14 |
15 |
16 |

17 | % for ctx in view_context.context_rev(): 18 | <% ctxr = ctx.without_id() %> 19 | :: ${ctxr.title} 20 | % if ctx.id: 21 | :: ${ctx.title} 22 | % endif 23 | % endfor 24 |

25 | % for template in templates: 26 | <%include file="${template}" /> 27 | % endfor 28 |
29 |
30 | 31 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/list.mako: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | % for field in view_context.list_fields: 10 | 11 | % endfor 12 | 13 | 14 | 15 | % for item in list_items: 16 | 17 | % for field in view_context.list_fields: 18 | <% value = getattr(item, field.attr) %> 19 | 20 | 37 | % endfor 38 | 39 | % endfor 40 | 41 |
${field.name}
21 | % if field.link: 22 | 23 | % endif 24 | 25 | % if value is True: 26 | ☑ 27 | % elif value is False: 28 | ☒ 29 | % elif value is not None: 30 | ${field.filter(value)} 31 | % endif 32 | 33 | % if field.link: 34 | 35 | % endif 36 |
42 |

43 | % if page > 0: 44 | << 45 | < 46 | % endif 47 | ${page} 48 | % if page < pages - 1: 49 | > 50 | >> 51 | % endif 52 |

53 | 54 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/order_payment.mako: -------------------------------------------------------------------------------- 1 | % if item.payment: 2 |
3 | % for key, value in item.payment.items():
4 | ${key}: ${value}
5 | % endfor
6 | 
7 | % endif 8 | 9 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/stats.mako: -------------------------------------------------------------------------------- 1 |

Bitcoin

2 | % if btcd: 3 |
    4 |
  • Balance: ${btcd.balance}
  • 5 |
  • Blocks: ${btcd.blocks}
  • 6 |
  • Difficulty: ${btcd.difficulty}
  • 7 |
  • Connections: ${btcd.connections}
  • 8 |
9 | % else: 10 |

Failed to connect to the bitcoin daemon.

11 | %endif 12 | 13 | % if graph: 14 |

Users

15 |
16 |
17 | 18 |

Income

19 |
20 |
21 |
22 |
23 | 24 |

Sessions

25 |
26 |
27 | % endif 28 | -------------------------------------------------------------------------------- /ccvpn/templates/admin/user_actions.mako: -------------------------------------------------------------------------------- 1 |

User actions

2 |
3 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /ccvpn/templates/config.ovpn.mako: -------------------------------------------------------------------------------- 1 | # +---------------------------+ 2 | # |Cognitive Cryptography VPN| 3 | # | http://vpn.ccrypto.org/ | 4 | # +---------------------------+ 5 | # ${profile.vpn_username} 6 | 7 | verb 4 8 | client 9 | tls-client 10 | script-security 2 11 | remote-cert-tls server 12 | dev tun 13 | resolv-retry infinite 14 | nobind 15 | persist-key 16 | persist-tun 17 | comp-lzo yes 18 | remote-random-hostname 19 | server-poll-timeout 4 20 | auth-user-pass 21 | 22 | remote ${remote} 23 | 24 | % if use_fragment: 25 | fragment 1300 26 | mssfix 1300 27 | % endif 28 | 29 | % if use_http_proxy and profile.use_http_proxy: 30 | http-proxy ${profile.use_http_proxy} 31 | % endif 32 | 33 | # Change default routes 34 | redirect-gateway def1 35 | 36 | % if use_ipv6: 37 | # Enable IPv6 38 | tun-ipv6 39 | route-ipv6 2000::/3 40 | %endif 41 | 42 | % if use_resolvconf: 43 | # Update DNS with resolvconf 44 | up /etc/openvpn/update-resolv-conf 45 | down /etc/openvpn/update-resolv-conf 46 | % endif 47 | 48 | 49 | 50 | ${openvpn_ca} 51 | 52 | -------------------------------------------------------------------------------- /ccvpn/templates/forgot_password.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Password Reset' %> 2 | <%inherit file="layout.mako" /> 3 | 4 |
5 |
6 |

${_('Password Reset')}

7 |
8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /ccvpn/templates/home.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="layout.mako" /> 2 | 3 | <%block name="headers"> 4 | 5 | 6 | 7 |
8 | % if motd: 9 |
10 |

${motd | n}

11 |
12 | % endif 13 | 14 |
15 |
16 | 17 |

${_('Unlimited')}

18 |

${_('Unlimited bandwidth')}.
19 | ${_('Uncensored')}.
20 | ${_('We have porn and pirates.')}

21 |
22 | 23 |
24 | 25 |

${_('Cheap')}

26 |

${eur_price}€ / ${btc_price}BTC ${_('per month!')}
27 | ${_('We accept Paypal, Bitcoins and Stripe.')}

28 |
29 | 30 |
31 | 32 |

${_('Secure')}

33 |

${_('Encrypted tunnel')} 34 | ${_('with an anonymous address.')|n}
35 | ${_('Supports DNSSEC and PFS.')}

36 |
37 | 38 |
39 | 40 |

${_('OpenVPN')}

41 |

${_('Secure, Free, easy to use. On:')}
42 |

    43 |
  • Windows, OSX
  • 44 |
  • GNU/Linux, BSD
  • 45 |
  • Android, iOS
  • 46 |
47 |

48 |
49 | 50 |
51 | 52 |

${_('Fast')}

53 |

1Gbps
54 | ${_('Compressed tunnel.')}
55 | ${_('Even on Youtube.')} 56 |

57 |
58 | 59 |
60 |
61 |
62 | 63 |

64 | ${_('Sign up')} 65 |

66 | 67 |
68 |
69 |

${_('Why?')}

70 |
    71 |
  • ${_('Hide any personal data found from your IP address')}
  • 72 |
  • ${_('Protect your privacy on open networks')}
  • 73 |
  • ${_('Compress traffic on slow connections')}
  • 74 |
  • ${_('Bypass restrictive firewalls')}
  • 75 |
  • ${_('Enable IPv6 on IPv4-only networks')}
  • 76 |
77 |
78 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /ccvpn/templates/layout.mako: -------------------------------------------------------------------------------- 1 | <%! 2 | title_ = None 3 | %> 4 | <% 5 | menuItems = [ 6 | [_('Home'), '/'], 7 | [_('Docs'), '/page/help'], 8 | [_('Gateways'), '/status'], 9 | ] 10 | 11 | if request.user: 12 | menuItems.append([_('Support'), '/tickets/']) 13 | 14 | if request.user and request.user.is_admin: 15 | menuItems.append([_('Admin'), '/admin/']) 16 | 17 | path = request.path 18 | for k in menuItems: 19 | if path == k[1] or (k[1] != '/' and k[1][-1] == '/' and path.startswith(k[1])): 20 | k.append(' class=\"selected\"') 21 | else: 22 | k.append('') 23 | 24 | settings = request.registry.settings 25 | use_https = settings.get('use_https', False) 26 | if use_https: 27 | ssl_port = settings.get('https_port', 443) 28 | ssl_url = request.current_route_url(_scheme='https', _port=ssl_port) 29 | 30 | if title: 31 | title_pre = title + ' - ' 32 | elif hasattr(self.attr, 'title') and self.attr.title: 33 | title_pre = self.attr.title + ' - ' 34 | else: 35 | title_pre = '' 36 | 37 | available_languages = request.registry.settings.get('available_languages') 38 | %> 39 | 40 | <%def name="pager(page, pages)"> 41 | % if pages > 1: 42 |

43 | % if page > 0: 44 | << 45 | < 46 | % endif 47 | ${page} 48 | % if page < pages - 1: 49 | > 50 | >> 51 | % endif 52 |

53 | % endif 54 | 55 | 56 | 57 | 58 | 59 | ${title_pre}CCrypto VPN 60 | 61 | <%block name="headers"> 62 | 63 | 64 | 65 | 66 |
67 |
68 |

69 | Cognitive Cryptography 70 | % if use_https and request.host_port != str(ssl_port): 71 | // ${_('HTTPS')} 72 | % endif 73 | // ${_('Service Status')} 74 | % if available_languages: 75 | | 76 | % for l in available_languages.split(): 77 | ${l.upper()} 78 | % endfor 79 | % endif 80 |

81 |
82 |
83 | % if request.user: 84 |

${_('Your account')} 85 | ${_('Logout')} 86 |

87 | % else: 88 |

${_('Sign up')} 89 | ${_('Log in')} 90 |

91 | % endif 92 |
93 |
94 |
95 | 96 |
97 | 101 | 108 |
109 |
110 | 111 |
112 | % for packed in request.session.pop_flash(): 113 | <% t, m = packed if len(packed) == 2 else ('info', packed) %> 114 |
115 |

${m}

116 |
117 | % endfor 118 | 119 | ${next.body()} 120 |
121 | 122 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /ccvpn/templates/login.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Log in' %> 2 | <%inherit file="layout.mako" /> 3 | 4 |
5 |
6 |

${_('Log in')}

7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 25 |
26 | 27 | -------------------------------------------------------------------------------- /ccvpn/templates/mail/expiry.mako: -------------------------------------------------------------------------------- 1 | CCrypto VPN - http://vpn.ccrypto.org/ 2 | ------------------------------------- 3 | 4 | Hello, 5 | 6 | % if user.paid_time_left.days > 0: 7 | Your account ${user.username} is going to expire in ${user.paid_days_left()} days. 8 | You can already renew it, the time will be added to your account. 9 | % else: 10 | Your account ${user.username} is expired. 11 | To continue using it, you will need to renew it. 12 | % endif 13 | 14 | If you have any question, you can contact us by replying to this email. 15 | 16 | -------------------------------------------------------------------------------- /ccvpn/templates/mail/password_reset.mako: -------------------------------------------------------------------------------- 1 | CCrypto VPN - http://vpn.ccrypto.org/ 2 | ------------------------------------- 3 | 4 | A password reset of your account ${user.username} has been requested \ 5 | by ${requested_by}. 6 | 7 | Follow this link to change your password: 8 | ${url} 9 | 10 | If you have not requested this password change, please ignore this email. 11 | For any questions please contact us by replying to this email. 12 | 13 | -------------------------------------------------------------------------------- /ccvpn/templates/mail/password_reset_done.mako: -------------------------------------------------------------------------------- 1 | CCrypto VPN - http://vpn.ccrypto.org/ 2 | ------------------------------------- 3 | 4 | The password of your account ${user.username} has been changed \ 5 | by ${changed_by}. 6 | 7 | If you have not changed your password or think your account may have been 8 | compromised, please contact us by replying to this email. 9 | 10 | -------------------------------------------------------------------------------- /ccvpn/templates/mail/tickets_updated.mako: -------------------------------------------------------------------------------- 1 | CCrypto VPN - http://vpn.ccrypto.org/ 2 | ------------------------------------- 3 | 4 | Hello, 5 | 6 | You ticket #${ticket.id} "${ticket.subject}" has been updated: 7 | ${url} 8 | 9 | -------------------------------------------------------------------------------- /ccvpn/templates/order.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="layout.mako" /> 2 | 3 | <%def name="autorefresh()"> 4 |

This page is updated every minute.

5 | 12 | 13 | 14 |
15 |

${_('Order')} #${o.id}

16 | 17 | % if o.paid: 18 | 24 | % else: 25 |
26 | % if o.method == o.METHOD.BITCOIN: 27 |

${_('Please send ${n} BTC to ${a}.', 28 | mapping={'n': o.amount - o.paid_amount, 'a': o.payment['btc_address']})} 29 |

30 | ${autorefresh()} 31 | % elif o.method == o.METHOD.PAYPAL: 32 |

${_('help_paypal_wait')}

33 | ${autorefresh()} 34 | % elif o.method == o.METHOD.STRIPE: 35 |
36 | 44 | 47 |
48 | % endif 49 |
50 | % endif 51 |
52 | 53 | -------------------------------------------------------------------------------- /ccvpn/templates/page.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="layout.mako" /> 2 | 3 |
4 |
5 | % if title: 6 |

${title}

7 | % endif 8 | ${content | n} 9 |
10 |
11 | 12 | -------------------------------------------------------------------------------- /ccvpn/templates/reset_password.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Password Reset' %> 2 | <%inherit file="layout.mako" /> 3 | 4 |
5 |
6 |

${_('Password Reset')}

7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 24 |
25 | 26 | -------------------------------------------------------------------------------- /ccvpn/templates/signup.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Sign up' %> 2 | <%inherit file="layout.mako" /> 3 | 4 |
5 |
6 |

${_('Sign up')}

7 |
8 | 9 | 10 |

${_('2 to 32 alphanumeric characters.')}

11 | 12 | 13 | 14 |

${_('Anything from 1 to 256 characters. Better choose a strong one.')}

15 | 16 | 17 | 18 | 19 | 20 | 21 |

${_('Optional.')} ${_('Used to recover your password and confirm stuff.')}

22 | 23 | 24 |
25 |
26 | 34 |
35 | Good luck, I'm behind 7 proxies. 36 |
37 |
38 | 39 | -------------------------------------------------------------------------------- /ccvpn/templates/status.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Status' %> 2 | <%inherit file="layout.mako" /> 3 |
4 |
5 |

${_('Gateways')}

6 | 7 |

${_('Stats')}

8 |
    9 |
  • ${_('We have ${n} active users.', mapping={'n': n_users})} 10 | ${_('${n} are using the VPN right now.', mapping={'n': n_connected})}
  • 11 |
  • ${_('We provide ${ns} servers in ${nc} countries,', 12 | mapping={'ns': len(gateways), 'nc': n_countries})} 13 | ${_('and a total bandwidth of ${bw}', mapping={'bw': total_bw})}
  • 14 |
15 | 16 |

${_('Gateways')}

17 |

gw.random.204vpn.net ${_('points to a random server.')}
18 | gw.[country].204vpn.net ${_('to a random server in a given country.')} 19 | ${_('(e.g. ${e})', mapping={'e': 'gw.fr.204vpn.net'})}

20 | % if gateways: 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | % for d in gateways: 30 | 33 | 35 | 36 | 37 | % endfor 38 | 39 |
${_('Host')}${_('ISP')} / ${_('Bandwidth')}${_('Location')}
31 | ${d.host_name} 32 | ${d.isp_name} / 34 | ${d.bps_formatted}${d.country.upper()}
40 | % endif 41 |
42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /ccvpn/templates/tickets/list.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'Tickets' %> 2 | <%inherit file="../layout.mako" /> 3 | 4 | <% 5 | def status(ticket): 6 | if ticket.closed: 7 | return _('Closed') 8 | 9 | if ticket.messages: 10 | last_message = ticket.messages[-1] 11 | if last_message.user_id == ticket.user_id: 12 | # Ticket owner replied 13 | return _('Open') 14 | 15 | # Staff replied 16 | date = last_message.create_date 17 | return _('Last reply: ${date}', mapping={'date': date}) 18 | %> 19 | 20 |
21 |
22 |

${_('Tickets')}

23 | 24 |

${_('New Ticket')} 25 | % if hide_closed: 26 | - ${_('Show closed tickets')} 27 | % else: 28 | - ${_('Hide closed tickets')} 29 | % endif 30 |

31 | 32 | % if tickets: 33 | 34 | 35 | 36 | 37 | 38 | 39 | % if request.user.is_support: 40 | 41 | % endif 42 | 43 | 44 | 45 | % for ticket in tickets: 46 | <% url = request.route_url('tickets_view', id=ticket.id) %> 47 | 48 | 49 | 50 | 51 | % if request.user.is_support: 52 | 55 | % endfor 56 | 57 |
#${_('ID')}${_('Subject')}${_('Status')}${_('User')}
#${ticket.id}${ticket.subject}${status(ticket)}${ticket.user.username} 53 | % endif 54 |
58 | % endif 59 |
60 |
61 | 62 | -------------------------------------------------------------------------------- /ccvpn/templates/tickets/new.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'New Ticket' %> 2 | <%inherit file="../layout.mako" /> 3 | 4 |
5 |
6 |

${_('New Ticket')}

7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /ccvpn/templates/tickets/view.mako: -------------------------------------------------------------------------------- 1 | <%! title = 'View Ticket' %> 2 | <%inherit file="../layout.mako" /> 3 | 4 |
5 |

Ticket: ${ticket.subject} 6 | % if ticket.closed: 7 | (${_('Closed')}) 8 | % endif 9 |

10 | 11 | % for message in ticket.messages: 12 | <% owned = 'ticket-message-owned' if message.user_id == ticket.user_id else '' %> 13 |
14 |

15 | Posted by 16 | % if request.user.is_support: 17 | ${message.user.username} 18 | % else: 19 | ${message.user.username} 20 | % endif 21 | 22 | on ${date_fmt(message.create_date)|n}. 23 |

24 |

25 | <%! import markupsafe %> 26 | <% content = str(markupsafe.escape(message.content)) %> 27 | ${content.replace('\n', '
') | n} 28 |

29 |
30 | % endfor 31 | 32 |
33 |
34 | % if ticket.closed: 35 | 36 | % else: 37 | 38 | % endif 39 | 40 | 41 | % if not ticket.closed: 42 | 43 | 44 | % endif 45 | 46 | % if request.user.id == ticket.user_id: 47 | 49 | 50 | % endif 51 | 52 | 53 |
54 |
55 |
56 | 57 | -------------------------------------------------------------------------------- /ccvpn/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from pyramid import testing 2 | from sqlalchemy import engine_from_config 3 | from pyramid.i18n import get_localizer, TranslationStringFactory 4 | from paste.deploy.loadwsgi import appconfig 5 | from ccvpn.models import DBSession, Base, get_user 6 | from ccvpn import Messages, referral_handler, methods 7 | import unittest 8 | import os 9 | 10 | 11 | class BaseTest(unittest.TestCase): 12 | @classmethod 13 | def setUpClass(cls): 14 | here = os.path.dirname(__file__) 15 | 16 | localtest = os.path.join(here, '../../', 'test.local.ini') 17 | if os.path.isfile(localtest): 18 | cls.settings = appconfig('config:' + localtest) 19 | else: 20 | test = os.path.join(here, '../../', 'test.ini') 21 | cls.settings = appconfig('config:' + test) 22 | 23 | cls.engine = engine_from_config(cls.settings, 'sqlalchemy.') 24 | 25 | def setUp(self): 26 | if not 'mako.directories' in self.settings: 27 | self.settings['mako.directories'] = 'ccvpn:templates/' 28 | if not 'mako.imports' in self.settings: 29 | self.settings['mako.imports'] = 'from ccvpn.filters import check' 30 | 31 | self.config = testing.setUp(settings=self.settings) 32 | self.config.include('pyramid_mailer.testing') 33 | self.config.include('pyramid_mako') 34 | self.config.include('pyramid_beaker') 35 | self.config.include('pyramid_mailer.testing') 36 | setup_routes(self.config) 37 | 38 | DBSession.remove() 39 | 40 | #self.engine = engine_from_config(settings, 'sqlalchemy.') 41 | self.conn = self.engine.connect() 42 | self.trans = self.conn.begin() 43 | 44 | DBSession.configure(bind=self.conn) 45 | Base.metadata.create_all(self.engine) 46 | self.session = DBSession 47 | 48 | def tearDown(self): 49 | testing.tearDown() 50 | self.trans.rollback() 51 | self.session.remove() 52 | self.conn.close() 53 | 54 | class DummyRequest(testing.DummyRequest): 55 | def __init__(self, *args, **kwargs): 56 | super().__init__(*args, **kwargs) 57 | self.messages = Messages(self) 58 | self.referrer = None 59 | set 60 | self.remote_addr = kwargs.get('remote_addr') 61 | self.translate = TranslationStringFactory('ccvpn') 62 | 63 | methods_objs = {} 64 | for c in methods.Method.__subclasses__(): 65 | obj = c({}) 66 | methods_objs[c.name] = obj 67 | methods_objs[c.id] = obj 68 | self.payment_methods = methods_objs 69 | 70 | referral_handler(self) 71 | 72 | @property 73 | def user(self): 74 | return get_user(self) 75 | 76 | from ccvpn.tests.filters import * # noqa 77 | from ccvpn.tests.models import * # noqa 78 | from ccvpn.tests.scripts import * # noqa 79 | from ccvpn.tests.views_account import * # noqa 80 | from ccvpn.tests.views_api import * # noqa 81 | from ccvpn.tests.views_order import * # noqa 82 | 83 | -------------------------------------------------------------------------------- /ccvpn/tests/filters.py: -------------------------------------------------------------------------------- 1 | from ccvpn import filters 2 | from ccvpn.tests import BaseTest 3 | 4 | 5 | class TestFilters(BaseTest): 6 | def test(self): 7 | self.assertIsInstance(filters.check('True'), str) 8 | self.assertIsInstance(filters.check('False'), str) 9 | 10 | -------------------------------------------------------------------------------- /ccvpn/tests/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | 4 | from pyramid import testing 5 | 6 | from ccvpn.models import DBSession, User, GiftCode 7 | from ccvpn import models 8 | from ccvpn.tests import BaseTest 9 | 10 | 11 | class TestModelsRandom(BaseTest): 12 | def test_access_token(self): 13 | t1 = models.random_access_token() 14 | t2 = models.random_access_token() 15 | self.assertEqual(32, len(t1)) 16 | self.assertEqual(len(t1), len(t2)) 17 | self.assertNotEqual(t1, t2) 18 | 19 | def test_gift_code(self): 20 | t1 = models.random_gift_code() 21 | t2 = models.random_gift_code() 22 | self.assertEqual(16, len(t1)) 23 | self.assertEqual(len(t1), len(t2)) 24 | self.assertNotEqual(t1, t2) 25 | 26 | def test_bytes(self): 27 | length = 42 28 | t1 = models.random_bytes(length) 29 | t2 = models.random_bytes(length) 30 | self.assertEqual(length, len(t1)) 31 | self.assertEqual(len(t1), len(t2)) 32 | self.assertNotEqual(t1, t2) 33 | 34 | 35 | class TestJSONEncodedDict(BaseTest): 36 | def test_bind(self): 37 | import json 38 | 39 | d = '' 40 | data = {'a': 1, 'b': 2} 41 | expected = json.dumps(data) 42 | 43 | dic = models.JSONEncodedDict() 44 | self.assertIsNone(dic.process_bind_param(None, d)) 45 | self.assertEqual(dic.process_bind_param(data, d), expected) 46 | 47 | def test_result(self): 48 | import json 49 | 50 | d = '' 51 | expected = {'a': 1, 'b': 2} 52 | data = json.dumps(expected) 53 | 54 | dic = models.JSONEncodedDict() 55 | self.assertEqual(dic.process_result_value(None, d), {}) 56 | self.assertEqual(dic.process_result_value(data, d), expected) 57 | 58 | 59 | class TestGetUser(BaseTest): 60 | def setUp(self): 61 | super().setUp() 62 | 63 | self.testuser = User(username='test', password='testpw') 64 | self.session.add(self.testuser) 65 | self.session.flush() 66 | 67 | def test(self): 68 | req = testing.DummyRequest() 69 | self.assertIsNone(models.get_user(req)) 70 | 71 | req.session['uid'] = 42 72 | self.assertIsNone(models.get_user(req)) 73 | 74 | req.session['uid'] = self.testuser.id 75 | self.assertIsInstance(models.get_user(req), User) 76 | 77 | 78 | class TestUserModel(BaseTest): 79 | def test_construct(self): 80 | user = User(username='test', password='pw') 81 | self.assertEqual(user.username, 'test') 82 | 83 | self.assertTrue(user.check_password('pw')) 84 | self.assertFalse(user.check_password('!pw')) 85 | 86 | def test_password(self): 87 | user = User() 88 | self.assertFalse(user.check_password('')) 89 | user.set_password('pw') 90 | self.assertTrue(user.check_password('pw')) 91 | self.assertFalse(user.check_password('!pw')) 92 | 93 | def test_paid(self): 94 | user = User() 95 | self.assertFalse(user.is_paid) 96 | self.assertEqual(user.paid_days_left(), 0) 97 | 98 | user.add_paid_time(datetime.timedelta(days=1)) 99 | self.assertTrue(user.is_paid) 100 | self.assertEqual(user.paid_days_left(), 1) 101 | 102 | user.paid_until = datetime.datetime.fromtimestamp(1) 103 | user.add_paid_time(datetime.timedelta(days=1)) 104 | self.assertTrue(user.is_paid) 105 | self.assertEqual(user.paid_days_left(), 1) 106 | 107 | def test_str(self): 108 | user = User(username='test') 109 | self.assertEqual(user.username, str(user)) 110 | 111 | def test_validation(self): 112 | self.assertTrue(User.validate_username('username')) 113 | self.assertFalse(User.validate_username('username/')) 114 | self.assertFalse(User.validate_username('')) 115 | self.assertFalse(User.validate_username(None)) 116 | 117 | self.assertTrue(User.validate_email('user@host')) 118 | self.assertFalse(User.validate_email('user host')) 119 | self.assertFalse(User.validate_email('')) 120 | self.assertFalse(User.validate_email(None)) 121 | 122 | self.assertTrue(User.validate_password('password')) 123 | self.assertFalse(User.validate_password('')) 124 | self.assertFalse(User.validate_password(None)) 125 | 126 | 127 | class TestGiftCodeModel(BaseTest): 128 | def setUp(self): 129 | super().setUp() 130 | 131 | self.u = User(username='freeuser', password='a') 132 | DBSession.add(self.u) 133 | self.session.flush() 134 | 135 | self.pu = User(username='paiduser', password='a') 136 | self.pu.add_paid_time(datetime.timedelta(days=30)) 137 | DBSession.add(self.pu) 138 | self.session.flush() 139 | 140 | def test_username_if_used(self): 141 | gc = GiftCode() 142 | self.assertIs(gc.username_if_used, False) 143 | gc.used = self.u.id 144 | gc.user = self.u 145 | self.assertEqual(gc.username_if_used, self.u.username) 146 | 147 | def test_use_freeonly(self): 148 | gc = GiftCode() 149 | gc.free_only = True 150 | self.assertRaises(models.AlreadyUsedGiftCode, gc.use, self.pu) 151 | gc.use(self.u) 152 | self.assertTrue(self.u.is_paid) 153 | 154 | def test_use_reuse(self): 155 | time = datetime.timedelta(days=30, hours=11) 156 | gc = GiftCode(time=time) 157 | gc.use(self.u) 158 | self.assertEqual(self.u.paid_time_left.days, time.days) 159 | self.assertRaises(models.AlreadyUsedGiftCode, gc.use, self.u) 160 | self.assertEqual(self.u.paid_time_left.days, time.days) 161 | gc.use(self.u, reuse=True) 162 | self.assertTrue(self.u.is_paid) 163 | self.assertEqual(self.u.paid_time_left.days, time.days*2) 164 | 165 | 166 | class TestUserModelWithDB(BaseTest): 167 | def setUp(self): 168 | super().setUp() 169 | 170 | u = User(username='test', email='test@host', password='a') 171 | DBSession.add(u) 172 | self.session.flush() 173 | 174 | def test_is_used(self): 175 | r = User.is_used('test', 'test@host') 176 | self.assertGreater(r[0], 0) 177 | self.assertGreater(r[1], 0) 178 | 179 | 180 | class TestPasswordResetTokenModel(BaseTest): 181 | def test_construct(self): 182 | prt = models.PasswordResetToken(42) 183 | self.assertEqual(prt.uid, 42) 184 | self.assertIsNotNone(prt.uid) 185 | self.assertGreater(prt.expire_date, datetime.datetime.now()) 186 | self.assertIsNotNone(prt.token) 187 | -------------------------------------------------------------------------------- /ccvpn/tests/scripts.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | import unittest 3 | from io import StringIO 4 | 5 | from webtest import TestApp 6 | from pyramid import testing 7 | from sqlalchemy import create_engine 8 | from pyramid_mailer import get_mailer 9 | 10 | from ccvpn.models import Base, User, Gateway, Profile 11 | from ccvpn import views, main, models, filters 12 | from ccvpn.scripts import initializedb, checkbtcorders, apiacl, expire_mail 13 | from ccvpn.tests import BaseTest 14 | 15 | 16 | class TestScriptInitDB(BaseTest): 17 | def test_usage(self): 18 | with self.assertRaises(SystemExit): 19 | initializedb.usage([''], out=StringIO()) 20 | 21 | def test_do(self): 22 | initializedb.initialize_db() 23 | 24 | u = self.session.query(User).first() 25 | self.assertIsNotNone(u) 26 | 27 | 28 | class TestScriptCheckBTCOrders(BaseTest): 29 | def setUp(self): 30 | super().setUp() 31 | self.testuser = User(username='test', password='testpw') 32 | self.session.add(self.testuser) 33 | self.testorder = models.Order(user=self.testuser.id, amount=2, 34 | method=models.Order.METHOD.BITCOIN, 35 | time=timedelta(days=30)) 36 | 37 | def test_usage(self): 38 | with self.assertRaises(SystemExit): 39 | checkbtcorders.usage([''], out=StringIO()) 40 | 41 | 42 | class TestScriptExpireMail(BaseTest): 43 | days = 7 44 | 45 | def setUp(self): 46 | super().setUp() 47 | 48 | def test_future(self): 49 | days = 7 50 | 51 | 52 | u_exp5 = User(username='exp5', password='.', email='.') 53 | u_exp5.add_paid_time(timedelta(days=5)) 54 | self.session.add(u_exp5) 55 | 56 | u_exp10 = User(username='exp10', password='.', email='.') 57 | u_exp10.add_paid_time(timedelta(days=10)) 58 | self.session.add(u_exp10) 59 | 60 | # Same, with last_expiry_notice 61 | u_exp5l = User(username='exp5l', password='.', email='.') 62 | u_exp5l.last_expiry_notice = datetime.now() - timedelta(days=1) 63 | u_exp5l.add_paid_time(timedelta(days=5)) 64 | self.session.add(u_exp5l) 65 | 66 | u_exp5ll = User(username='exp5ll', password='.', email='.') 67 | u_exp5ll.last_expiry_notice = datetime.now() - timedelta(days=30) 68 | u_exp5ll.add_paid_time(timedelta(days=5)) 69 | self.session.add(u_exp5ll) 70 | 71 | u_exp5 = self.session.query(User).filter_by(username='exp5').one() 72 | u_exp10 = self.session.query(User).filter_by(username='exp10').one() 73 | u_exp5l = self.session.query(User).filter_by(username='exp5l').one() 74 | u_exp5ll = self.session.query(User).filter_by(username='exp5ll').one() 75 | 76 | users = expire_mail.get_future_expire(days) 77 | self.assertIn(u_exp5, users) 78 | self.assertNotIn(u_exp10, users) 79 | self.assertNotIn(u_exp5l, users) 80 | self.assertIn(u_exp5ll, users) 81 | 82 | def test_expired(self): 83 | u_exp = User(username='exp', password='.', email='.', 84 | paid_until=datetime.now()-timedelta(days=5)) 85 | u_expl = User(username='expl', password='.', email='.', 86 | paid_until=datetime.now()-timedelta(days=5), 87 | last_expiry_notice=datetime.now()-timedelta(days=12)) 88 | u_expll = User(username='expll', password='.', email='.', 89 | paid_until=datetime.now()-timedelta(days=5), 90 | last_expiry_notice=datetime.now()-timedelta(days=1)) 91 | u_expf = User(username='expf', password='.', email='.', 92 | paid_until=datetime.now()+timedelta(days=5)) 93 | self.session.add_all([u_exp, u_expl, u_expll, u_expf]) 94 | 95 | u_exp = self.session.query(User).filter_by(username='exp').one() 96 | u_expl = self.session.query(User).filter_by(username='expl').one() 97 | u_expll = self.session.query(User).filter_by(username='expll').one() 98 | u_expf = self.session.query(User).filter_by(username='expf').one() 99 | 100 | users = expire_mail.get_expired() 101 | self.assertIn(u_exp, users) 102 | self.assertIn(u_expl, users) 103 | self.assertNotIn(u_expll, users) 104 | self.assertNotIn(u_expf, users) 105 | 106 | def test_send(self): 107 | registry = self.config.registry 108 | mailer = get_mailer(registry) 109 | 110 | u = User(username='test_user_1', email='test1@example.com', 111 | paid_until=datetime.now()+timedelta(days=4)) 112 | expire_mail.send_notice(u, mailer) 113 | self.assertEqual(len(mailer.outbox), 1) 114 | self.assertIn('test_user_1', mailer.outbox[0].body) 115 | self.assertIn('test1@example.com', mailer.outbox[0].recipients) 116 | 117 | u = User(username='test_user_2', email='test2@example.com', 118 | paid_until=datetime.now()+timedelta(days=-4)) 119 | expire_mail.send_notice(u, mailer) 120 | self.assertEqual(len(mailer.outbox), 2) 121 | self.assertIn('test_user_2', mailer.outbox[1].body) 122 | self.assertIn('test2@example.com', mailer.outbox[1].recipients) 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /ccvpn/tests/views_order.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | 4 | from pyramid import testing 5 | from pyramid import httpexceptions 6 | 7 | from ccvpn.models import User, Order, GiftCode 8 | from ccvpn import views, setup_routes 9 | from ccvpn.tests import BaseTest, DummyRequest 10 | 11 | 12 | class TestOrderView(BaseTest): 13 | def setUp(self): 14 | super().setUp() 15 | 16 | self.testuser = User(username='test', password='testpw') 17 | self.session.add(self.testuser) 18 | self.testadmin = User(username='testadmin', password='testpw') 19 | self.testadmin.is_admin = True 20 | self.session.add(self.testadmin) 21 | self.session.flush() 22 | self.testcode = GiftCode(datetime.timedelta(days=7)) 23 | self.session.add(self.testcode) 24 | self.session.flush() 25 | 26 | def test_post_code(self): 27 | req = DummyRequest(post={'code': self.testcode.code}) 28 | req.session['uid'] = self.testuser.id 29 | resp = views.order.order_post(req) 30 | self.assertIsInstance(resp, httpexceptions.HTTPSeeOther) 31 | self.assertTrue(resp.location.endswith('/account/')) 32 | self.assertTrue(self.testuser.is_paid) 33 | until = self.testuser.paid_until 34 | 35 | resp = views.order.order_post(req) 36 | self.assertIsInstance(resp, httpexceptions.HTTPSeeOther) 37 | self.assertTrue(resp.location.endswith('/account/')) 38 | self.assertTrue(self.testuser.is_paid) 39 | self.assertEquals(self.testuser.paid_until, until) 40 | 41 | def test_post_admin(self): 42 | req = DummyRequest(post={'method': 'admin', 'time': '3'}) 43 | 44 | req.session['uid'] = self.testuser.id 45 | resp = views.order.order_post(req) 46 | self.assertIsInstance(resp, httpexceptions.HTTPBadRequest) 47 | self.assertFalse(self.testuser.is_paid) 48 | 49 | req = DummyRequest(post={'method': 'admin', 'time': '3'}) 50 | req.session['uid'] = self.testadmin.id 51 | resp = views.order.order_post(req) 52 | self.assertIsInstance(resp, httpexceptions.HTTPSeeOther) 53 | self.assertTrue(resp.location.endswith('/account/')) 54 | self.assertTrue(self.testadmin.is_paid) 55 | 56 | def test_post_unknown_code(self): 57 | req = DummyRequest(post={'code': 'fail'}) 58 | req.session['uid'] = self.testuser.id 59 | resp = views.order.order_post(req) 60 | self.assertIsInstance(resp, httpexceptions.HTTPSeeOther) 61 | self.assertTrue(resp.location.endswith('/account/')) 62 | self.assertFalse(self.testuser.is_paid) 63 | 64 | def test_post_order_paypal(self): 65 | req = DummyRequest(post={ 66 | 'method': 'paypal', 67 | 'time': '1' 68 | }) 69 | req.session['uid'] = self.testuser.id 70 | resp = views.order.order_post(req) 71 | self.assertIsInstance(resp, httpexceptions.HTTPSeeOther) 72 | self.assertIn('paypal.com', resp.location) 73 | 74 | def test_post_order_paypal_invalid_method(self): 75 | req = DummyRequest(post={ 76 | 'method': 'dogecoin', 77 | 'time': '1' 78 | }) 79 | req.session['uid'] = self.testuser.id 80 | resp = views.order.order_post(req) 81 | self.assertIsInstance(resp, httpexceptions.HTTPBadRequest) 82 | 83 | def test_post_order_paypal_invalid_time(self): 84 | req = DummyRequest(post={ 85 | 'method': 'paypal', 86 | 'time': 'one' 87 | }) 88 | req.session['uid'] = self.testuser.id 89 | resp = views.order.order_post(req) 90 | self.assertIsInstance(resp, httpexceptions.HTTPSeeOther) 91 | 92 | def test_view_paypal(self): 93 | testorder = Order(user=self.testuser.id, amount=1, 94 | method=Order.METHOD.PAYPAL, 95 | time=datetime.timedelta(days=30)) 96 | self.session.add(testorder) 97 | self.session.flush() 98 | 99 | req = DummyRequest() 100 | req.session['uid'] = self.testuser.id 101 | req.matchdict['hexid'] = '%x' % testorder.id 102 | resp = views.order.order_view(req) 103 | self.assertIsInstance(resp, dict) 104 | self.assertEqual(resp['o'], testorder) 105 | 106 | def test_view_btc(self): 107 | testorder = Order(user=self.testuser.id, amount=1, 108 | method=Order.METHOD.BITCOIN, 109 | time=datetime.timedelta(days=30)) 110 | testorder.payment = {'btc_address': 'TESTADDRESS'} 111 | self.session.add(testorder) 112 | self.session.flush() 113 | 114 | req = DummyRequest() 115 | req.session['uid'] = self.testuser.id 116 | req.matchdict['hexid'] = '%x' % testorder.id 117 | resp = views.order.order_view(req) 118 | self.assertIsInstance(resp, dict) 119 | self.assertEqual(resp['o'], testorder) 120 | 121 | def test_view_not_found(self): 122 | req = DummyRequest() 123 | req.session['uid'] = self.testuser.id 124 | req.matchdict['hexid'] = '%x' % 4242 125 | resp = views.order.order_view(req) 126 | self.assertIsInstance(resp, httpexceptions.HTTPNotFound) 127 | 128 | def test_view_not_owned(self): 129 | otheruser = User(username='othertest', password='testpw') 130 | self.session.add(otheruser) 131 | self.session.flush() 132 | 133 | testorder = Order(user=otheruser.id, amount=1, 134 | method=Order.METHOD.PAYPAL, 135 | time=datetime.timedelta(days=30)) 136 | self.session.add(testorder) 137 | self.session.flush() 138 | 139 | req = DummyRequest() 140 | req.session['uid'] = self.testuser.id 141 | req.matchdict['hexid'] = '%x' % testorder.id 142 | resp = views.order.order_view(req) 143 | self.assertIsInstance(resp, httpexceptions.HTTPUnauthorized) 144 | 145 | 146 | -------------------------------------------------------------------------------- /ccvpn/views/__init__.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import markdown 3 | import os 4 | import logging 5 | from pyramid.view import view_config 6 | from pyramid.httpexceptions import HTTPOk, HTTPNotFound 7 | from sqlalchemy import func 8 | from mako.lookup import TemplateLookup 9 | import mako.exceptions 10 | logger = logging.getLogger(__name__) 11 | 12 | from ccvpn.models import DBSession, User, IcingaError, IcingaQuery, Gateway, VPNSession 13 | from ccvpn.views import account, admin, api, order # noqa 14 | 15 | 16 | @view_config(context=Exception) 17 | def error_view(exc, request): 18 | logger.exception('Exception', exc_info=exc) 19 | raise 20 | 21 | @view_config(route_name='home', renderer='home.mako') 22 | def home(request): 23 | settings = request.registry.settings 24 | return { 25 | 'eur_price': float(settings.get('paypal.month_price', 2)), 26 | 'btc_price': float(settings.get('bitcoin.month_price', 0.02)), 27 | 'motd': settings.get('motd'), 28 | } 29 | 30 | 31 | @view_config(route_name='ca_crt') 32 | def ca_crt(request): 33 | return HTTPOk(body=account.openvpn_ca) 34 | 35 | 36 | @view_config(route_name='page', renderer='page.mako') 37 | def page(request): 38 | root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 39 | pagesdir = os.path.join(root, 'pages/') 40 | basename = pagesdir + request.matchdict['page'] 41 | irc_username = request.user.username if request.user else '?' 42 | 43 | try: 44 | translated_file = basename + '.' + request.locale_name + '.md' 45 | fallback_file = basename + '.md' 46 | if os.path.isfile(translated_file): 47 | template = translated_file 48 | elif os.path.isfile(fallback_file): 49 | template = fallback_file 50 | else: 51 | raise FileNotFoundError() 52 | 53 | with open(template, encoding='utf8') as template_f: 54 | mdt = template_f.read() 55 | mdt = mdt.replace('${irc_username}', irc_username) 56 | md = markdown.Markdown(extensions=['toc', 'meta', 57 | 'codehilite(noclasses=True)']) 58 | content = md.convert(mdt) 59 | title = md.Meta['title'][0] if 'title' in md.Meta else None 60 | return {'content': content, 'title': title} 61 | except FileNotFoundError: 62 | return HTTPNotFound() 63 | 64 | 65 | def format_bps(bits): 66 | multiples = ((1e9, 'G'), (1e6, 'M'), (1e3, 'K'), (0, '')) 67 | for d, m in multiples: 68 | if bits < d: 69 | continue 70 | n = bits / (d or 1) 71 | return '{:2g}{}bps'.format(n, m) 72 | 73 | 74 | @view_config(route_name='status', renderer='status.mako') 75 | def status(request): 76 | settings = request.registry.settings 77 | domain = settings.get('net_domain', '') 78 | gateways = DBSession.query(Gateway) \ 79 | .filter_by(enabled=True) \ 80 | .order_by(Gateway.country, Gateway.name) \ 81 | .all() 82 | l = list(gateways) 83 | 84 | for host in l: 85 | host.host_name = '%s-%s.%s'%(host.country, host.name, domain) 86 | host.bps_formatted = format_bps(host.bps) 87 | 88 | return { 89 | 'gateways': l, 90 | 'n_users': DBSession.query(func.count(User.id)) 91 | .filter_by(is_paid=True).scalar(), 92 | 'n_connected': DBSession.query(func.count(VPNSession.id)) \ 93 | .filter(VPNSession.is_online==True).scalar(), 94 | 'n_countries': len(set(i.country for i in l)), 95 | 'total_bw': format_bps(sum(i.bps for i in l)), 96 | } 97 | 98 | -------------------------------------------------------------------------------- /ccvpn/views/api.py: -------------------------------------------------------------------------------- 1 | import json 2 | import datetime 3 | 4 | import transaction 5 | from pyramid.view import view_config 6 | from ccvpn.models import DBSession, User, Gateway, Profile, VPNSession 7 | from pyramid.httpexceptions import ( 8 | HTTPOk, 9 | HTTPBadRequest, HTTPForbidden, HTTPNotFound, HTTPForbidden 10 | ) 11 | 12 | gw_versions = { 13 | 'udpl': 1, 14 | 'tcp': 2, 15 | 'socks': 3, 16 | 'udp': 4, 17 | } 18 | 19 | def require_api_token(function=None): 20 | def _dec(view_func): 21 | def _func(request): 22 | if not 'X-Gateway-Token' in request.headers: 23 | return HTTPBadRequest('require X-Gateway-Token header.') 24 | if not 'X-Gateway-Version' in request.headers: 25 | return HTTPBadRequest('require X-Gateway-Version header.') 26 | if request.headers['X-Gateway-Version'] not in gw_versions: 27 | return HTTPBadRequest('invalid X-Gateway-Version.') 28 | version = gw_versions[request.headers['X-Gateway-Version']] 29 | 30 | token = request.headers['X-Gateway-Token'] 31 | gw = DBSession.query(Gateway) \ 32 | .filter_by(token=token).first() 33 | if not gw: 34 | return HTTPForbidden('Unknown token.') 35 | if not gw.enabled: 36 | return HTTPForbidden('Gateway disabled.') 37 | 38 | ra = request.remote_addr 39 | if gw.ipv4 == ra or gw.ipv6 == ra or gw.ipv6 == '::ffff:'+ra: 40 | request.gw = gw 41 | request.gw_version = version 42 | return view_func(request) 43 | return HTTPForbidden('Source address not authorized for this' 44 | 'gateway.') 45 | return _func 46 | if function is None: 47 | return _dec 48 | else: 49 | return _dec(function) 50 | 51 | 52 | @view_config(route_name='api_gateway_auth', request_method='POST') 53 | @require_api_token 54 | def api_gateway_auth(request): 55 | try: 56 | fullname = request.POST['username'] 57 | password = request.POST['password'] 58 | except KeyError: 59 | return HTTPBadRequest('Require username/password') 60 | 61 | if '/' in fullname: 62 | username, profilename = fullname.split('/', 1) 63 | else: 64 | username = fullname 65 | profilename = None 66 | 67 | user = DBSession.query(User).filter_by(username=username).first() 68 | if not user or not user.check_password(password) or not user.is_active: 69 | return HTTPForbidden('Invalid username or password.') 70 | if not user.is_paid: 71 | return HTTPForbidden('Free account') 72 | 73 | if profilename: 74 | profile = DBSession.query(Profile) \ 75 | .filter_by(name=profilename, uid=user.id).first() 76 | if not profile: 77 | return HTTPForbidden('Unknown profile') 78 | return HTTPOk(empty_body=True) 79 | 80 | 81 | @view_config(route_name='api_gateway_disconnect', request_method='POST') 82 | @require_api_token 83 | def api_gateway_disconnect(request): 84 | try: 85 | fullname = request.POST['username'] 86 | bytes_up = request.POST['bytes_up'] 87 | bytes_down = request.POST['bytes_down'] 88 | except KeyError: 89 | return HTTPBadRequest('Require username/bytes_up/bytes_down') 90 | 91 | if '/' in fullname: 92 | username, profilename = fullname.split('/', 1) 93 | else: 94 | username = fullname 95 | profilename = None 96 | 97 | sesq = DBSession.query(VPNSession) \ 98 | .filter(VPNSession.disconnect_date == None) \ 99 | .filter(VPNSession.gateway_id == request.gw.id) \ 100 | .filter(VPNSession.gateway_version == request.gw_version) \ 101 | .filter(User.username == username) \ 102 | .join(User) 103 | if profilename: 104 | sesq = sesq.filter(Profile.uid == User.id) \ 105 | .filter(Profile.name == profilename) \ 106 | .join(Profile) 107 | sessions = list(sesq.all()) 108 | 109 | # First, we close every sessions except the last 110 | # This can happen in /disconnect is not called 111 | for sess in sessions[:-1]: 112 | sess.disconnect_date = datetime.datetime.now() 113 | sess.bytes_up = 0 114 | sess.bytes_down = 0 115 | 116 | # Now we can close the right session and save bw stats 117 | if sessions: 118 | sess = sessions[-1] 119 | sess.disconnect_date = datetime.datetime.now() 120 | sess.bytes_up = bytes_up 121 | sess.bytes_down = bytes_down 122 | return HTTPOk(empty_body=True) 123 | 124 | 125 | @view_config(route_name='api_gateway_connect', request_method='POST') 126 | @require_api_token 127 | def api_gateway_connect(request): 128 | try: 129 | fullname = request.POST['username'] 130 | client_addr = request.POST['remote_addr'] 131 | internal_ip4 = request.POST['internal_ip4'] 132 | internal_ip6 = request.POST['internal_ip6'] 133 | except KeyError: 134 | return HTTPBadRequest('Require username/remote_addr') 135 | 136 | if '/' in fullname: 137 | username, profilename = fullname.split('/', 1) 138 | else: 139 | username = fullname 140 | profilename = None 141 | 142 | user = DBSession.query(User).filter_by(username=username).first() 143 | if not user or not user.is_active or not user.is_paid: 144 | return HTTPForbidden('Invalid account') 145 | 146 | sess = VPNSession() 147 | sess.gateway_id = request.gw.id 148 | sess.gateway_version = request.gw_version 149 | sess.user_id = user.id 150 | sess.remote_addr = client_addr 151 | sess.internal_ip4 = internal_ip4 152 | sess.internal_ip6 = internal_ip6 153 | 154 | if profilename: 155 | profile = DBSession.query(Profile) \ 156 | .filter_by(name=profilename, uid=user.id).first() 157 | if not profile: 158 | return HTTPForbidden('Unknown profile') 159 | sess.profile_id = profile.id 160 | 161 | DBSession.add(sess) 162 | 163 | params = {} 164 | 165 | return HTTPOk(body=json.dumps(params)) 166 | 167 | 168 | @view_config(route_name='api_public_gateways', request_method='GET') 169 | def api_public_gateways(request): 170 | domain = request.registry.settings.get('net_domain', '') 171 | q = DBSession.query(Gateway) 172 | 173 | show_disabled = request.GET.get('show_disabled') 174 | 175 | country = request.GET.get('country') 176 | hostname = request.GET.get('hostname') 177 | 178 | if country: 179 | q = q.filter(Gateway.country == country) 180 | 181 | if hostname: 182 | if '-' in hostname: 183 | hn_country, hn_name = hostname.split('-', 1) 184 | q = q.filter(Gateway.country == hn_country) 185 | q = q.filter(Gateway.name == hn_name) 186 | elif country: 187 | q = q.filter(Gateway.name == hostname) 188 | else: 189 | return HTTPOk(body=json.dumps([]), content_type="application/json") 190 | 191 | 192 | if not show_disabled: 193 | q = q.filter(Gateway.enabled == True) 194 | 195 | def out(g): 196 | return { 197 | 'hostname': g.country + '-' + g.name, 198 | 'fqdn': g.country + '-' + g.name + '.' + domain, 199 | 'country': g.country, 200 | 'bandwidth': g.bps, 201 | 'ipv4': g.ipv4, 202 | 'ipv6': g.ipv6, 203 | 'enabled': g.enabled, 204 | } 205 | 206 | r = [out(g) for g in q.all()] 207 | return HTTPOk(body=json.dumps(r), content_type="application/json") 208 | 209 | -------------------------------------------------------------------------------- /ccvpn/views/order.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound 4 | from pyramid.view import view_config 5 | from pyramid.httpexceptions import ( 6 | HTTPSeeOther, 7 | HTTPBadRequest, HTTPNotFound, HTTPUnauthorized 8 | ) 9 | from ccvpn.models import ( 10 | DBSession, 11 | GiftCode, AlreadyUsedGiftCode, 12 | Order, 13 | ) 14 | 15 | 16 | def order_post_gc(request, code): 17 | _ = request.translate 18 | try: 19 | gc = GiftCode.one(code=code) 20 | gc.use(request.user) 21 | 22 | time = gc.time.days 23 | request.messages.info(_('OK! Added ${time} days to your account.', 24 | mapping={'time': time})) 25 | DBSession.flush() 26 | except (NoResultFound, MultipleResultsFound): 27 | request.messages.error(_('Unknown gift code.')) 28 | except AlreadyUsedGiftCode: 29 | request.messages.error(_('Gift code already used.')) 30 | return HTTPSeeOther(location=request.route_url('account')) 31 | 32 | 33 | @view_config(route_name='order_post', permission='logged') 34 | def order_post(request): 35 | _ = request.translate 36 | code = request.POST.get('code') 37 | if code: 38 | return order_post_gc(request, code) 39 | 40 | times = (1, 3, 6, 12) 41 | try: 42 | method_name = request.POST.get('method') 43 | time_months = int(request.POST.get('time')) 44 | except (ValueError, TypeError): 45 | return HTTPSeeOther(location=request.route_url('account')) 46 | 47 | if method_name == 'admin' and request.user.is_admin: 48 | time = datetime.timedelta(days=30 * time_months) 49 | request.user.add_paid_time(time) 50 | return HTTPSeeOther(location=request.route_url('account')) 51 | 52 | method = request.payment_methods.get(method_name) 53 | if not method or time_months not in times: 54 | return HTTPBadRequest('Invalid method/time') 55 | 56 | time = datetime.timedelta(days=30 * time_months) 57 | 58 | o = method.init(request.user, time) 59 | DBSession.add(o) 60 | DBSession.flush() 61 | return method.start(request, o) 62 | 63 | 64 | @view_config(route_name='order_view', renderer='order.mako', 65 | permission='logged') 66 | def order_view(request): 67 | _ = request.translate 68 | id = int(request.matchdict['hexid'], 16) 69 | o = DBSession.query(Order).filter_by(id=id).first() 70 | if not o: 71 | return HTTPNotFound() 72 | if not request.user.is_admin and request.user.id != o.uid: 73 | return HTTPUnauthorized() 74 | method = request.payment_methods.get(o.method) 75 | r = {'o': o} 76 | try: 77 | r.update(method.view(request, o)) 78 | except NotImplementedError: 79 | pass 80 | return r 81 | 82 | 83 | @view_config(route_name='order_callback') 84 | def order_callback(request): 85 | _ = request.translate 86 | id = int(request.matchdict['hexid'], 16) 87 | o = DBSession.query(Order).filter_by(id=id).first() 88 | if not o: 89 | return HTTPNotFound() 90 | 91 | method = request.payment_methods.get(o.method) 92 | r = method.callback(request, o) 93 | DBSession.flush() 94 | return r 95 | 96 | 97 | -------------------------------------------------------------------------------- /ccvpn/views/tickets.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm.exc import NoResultFound 2 | from pyramid.view import view_config 3 | from pyramid.renderers import render 4 | from pyramid.httpexceptions import HTTPSeeOther, HTTPNotFound 5 | from pyramid_mailer import get_mailer 6 | from pyramid_mailer.message import Message 7 | 8 | from ccvpn.models import ( 9 | DBSession, 10 | User, Profile, PasswordResetToken, Gateway, VPNSession, Order, 11 | Ticket, TicketMessage, 12 | random_access_token 13 | ) 14 | 15 | 16 | @view_config(route_name='tickets_list', renderer='tickets/list.mako', 17 | permission='logged') 18 | def tickets_list(request): 19 | _ = request.translate 20 | 21 | tickets = DBSession.query(Ticket) 22 | 23 | if not request.user.is_admin: 24 | tickets = tickets.filter_by(user_id=request.user.id) 25 | 26 | hide_closed = 'hide_closed' in request.GET 27 | if hide_closed: 28 | tickets = tickets.filter_by(closed=False) 29 | 30 | tickets = tickets.order_by(Ticket.create_date.desc()).all() 31 | 32 | return {'tickets': tickets, 'hide_closed': hide_closed} 33 | 34 | 35 | @view_config(route_name='tickets_view', renderer='tickets/view.mako', 36 | permission='logged') 37 | def tickets_view(request): 38 | _ = request.translate 39 | 40 | id = request.matchdict.get('id') 41 | try: 42 | ticket_q = DBSession.query(Ticket).filter_by(id=id) 43 | if not request.user.is_admin: 44 | ticket_q = ticket_q.filter_by(user_id=request.user.id) 45 | ticket = ticket_q.one() 46 | except NoResultFound: 47 | return HTTPNotFound() 48 | 49 | if request.method != 'POST': 50 | return {'ticket': ticket} 51 | 52 | redirect = HTTPSeeOther(location=request.route_url('tickets_view', id=id)) 53 | 54 | try: 55 | body = request.POST['message'] 56 | close = 'close' in request.POST 57 | subscribe = 'subscribe' in request.POST 58 | except KeyError: 59 | return redirect 60 | 61 | if body: 62 | errors = [] 63 | 64 | if len(body) < 1: 65 | errors.append(_('Message empty')) 66 | if len(body) > 2000: 67 | errors.append(_('Message too long')) 68 | 69 | if errors: 70 | for e in errors: 71 | request.messages.error(e) 72 | return {'ticket': ticket} 73 | 74 | message = TicketMessage() 75 | message.user_id = request.user.id 76 | message.content = body 77 | message.ticket_id = ticket.id 78 | DBSession.add(message) 79 | 80 | if ticket.close: 81 | # We re-open it 82 | ticket.closed = False 83 | 84 | if ticket.notify_owner and ticket.user_id != message.user_id and ticket.user.email: 85 | mailer = get_mailer(request) 86 | body = render('mail/tickets_updated.mako', { 87 | 'user': ticket.user, 88 | 'ticket': ticket, 89 | 'url': request.route_url('tickets_view', id=ticket.id), 90 | }, request=request) 91 | message = Message(subject=_('CCVPN: Ticket update'), 92 | recipients=[ticket.user.email], 93 | body=body) 94 | mailer.send(message) 95 | 96 | if request.user.id == ticket.user_id: 97 | ticket.notify_owner = subscribe 98 | 99 | if not ticket.closed and close: 100 | ticket.close() 101 | 102 | return redirect 103 | 104 | 105 | @view_config(route_name='tickets_new', renderer='tickets/new.mako', 106 | permission='logged') 107 | def tickets_new(request): 108 | _ = request.translate 109 | 110 | if request.method != 'POST': 111 | return {} 112 | 113 | try: 114 | subject = request.POST['subject'] 115 | body = request.POST['message'] 116 | subscribe = 'subscribe' in request.POST 117 | except KeyError: 118 | return {} 119 | 120 | errors = [] 121 | 122 | if len(subject) < 1: 123 | errors.append(_('Subject empty')) 124 | if len(subject) > 40: 125 | errors.append(_('Subject too long')) 126 | 127 | if len(body) < 1: 128 | errors.append(_('Message empty')) 129 | if len(body) > 2000: 130 | errors.append(_('Message too long')) 131 | 132 | if errors: 133 | for e in errors: 134 | request.messages.error(e) 135 | return {'subject': subject, 'message': body} 136 | 137 | ticket = Ticket() 138 | ticket.user_id = request.user.id 139 | ticket.subject = subject 140 | ticket.notify_owner = subscribe 141 | 142 | DBSession.add(ticket) 143 | DBSession.flush() 144 | 145 | message = TicketMessage() 146 | message.ticket_id = ticket.id 147 | message.user_id = request.user.id 148 | message.content = body 149 | DBSession.add(message) 150 | 151 | url = request.route_url('tickets_view', id=ticket.id) 152 | return HTTPSeeOther(location=url) 153 | 154 | -------------------------------------------------------------------------------- /cron_checkbtcorders.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CCVPN_PATH="$( cd "$( dirname "$0" )" && pwd )" 4 | PATH="$HOME/.local/bin/:$PATH" 5 | 6 | if [ -z "$1" ]; then 7 | echo "usage: cron_checkbtcorders.sh " 8 | exit 1 9 | fi 10 | 11 | ccvpn_checkbtcorders $1 >> $CCVPN_PATH/cron_checkbtcorders.log 2>&1 12 | 13 | -------------------------------------------------------------------------------- /development.ini: -------------------------------------------------------------------------------- 1 | ### 2 | # app configuration 3 | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html 4 | ### 5 | 6 | [app:main] 7 | use = egg:ccvpn 8 | 9 | pyramid.reload_templates = true 10 | pyramid.debug_authorization = false 11 | pyramid.debug_notfound = false 12 | pyramid.debug_routematch = false 13 | pyramid.default_locale_name = en 14 | pyramid.includes = 15 | pyramid_debugtoolbar 16 | 17 | sqlalchemy.url = sqlite:///%(here)s/ccvpn.sqlite 18 | 19 | # By default, the toolbar only appears for clients from IP addresses 20 | # '127.0.0.1' and '::1'. 21 | # debugtoolbar.hosts = 127.0.0.1 ::1 22 | 23 | # Put a HTTPS link when using HTTP ? 24 | use_https = true 25 | # HTTPS port to use (default: 443) 26 | #https_port = 443 27 | 28 | # OpenVPN gateway public address 29 | openvpn.gateway = gw.vpn.ccrypto.org 30 | 31 | session.type = file 32 | session.data_dir = %(here)s/.tmp/session_data 33 | session.lock_dir = %(here)s/.tmp/session_lock 34 | session.key = session 35 | session.cookie_on_exception = true 36 | 37 | mail.host = localhost 38 | mail.port = 25 39 | mail.default_sender = support@ccrypto.org 40 | 41 | # OpenVPN CA certificate to be embedded into .ovpn files 42 | openvpn.ca-cert = %(here)s/ca.crt 43 | 44 | # Bitcoin settings 45 | # empty user = connect to local daemon 46 | bitcoin.user = 47 | bitcoin.password = 48 | bitcoin.host = localhost 49 | bitcoin.port = 8332 50 | bitcoin.account = ccvpn2 51 | bitcoin.month_price = 0.2 52 | 53 | # Paypal settings 54 | paypal.title = CCrypto VPN 55 | #paypal.header_image = 56 | paypal.currency = EUR 57 | paypal.address = paypal@ccrypto.org 58 | 59 | # paypal.receiver will be used to check address when notified 60 | # if you use a secondary address for paypal.address, put your primary here. 61 | paypal.receiver = paypal@ccrypto.org 62 | 63 | # Paypal sandbox API 64 | paypal.test = true 65 | 66 | [alembic] 67 | script_location = alembic/ 68 | pylons_config_file = ./development.ini 69 | 70 | [pshell] 71 | m = ccvpn.models 72 | dbs = ccvpn.models.DBSession 73 | 74 | ### 75 | # wsgi server configuration 76 | ### 77 | 78 | [server:main] 79 | use = egg:waitress#main 80 | host = 0.0.0.0 81 | port = 6543 82 | 83 | ### 84 | # logging configuration 85 | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html 86 | ### 87 | 88 | [loggers] 89 | keys = root, ccvpn, sqlalchemy 90 | 91 | [handlers] 92 | keys = console 93 | 94 | [formatters] 95 | keys = generic 96 | 97 | [logger_root] 98 | level = INFO 99 | handlers = console 100 | 101 | [logger_ccvpn] 102 | level = DEBUG 103 | handlers = 104 | qualname = ccvpn 105 | 106 | [logger_sqlalchemy] 107 | level = INFO 108 | handlers = 109 | qualname = sqlalchemy.engine 110 | # "level = INFO" logs SQL queries. 111 | # "level = DEBUG" logs SQL queries and results. 112 | # "level = WARN" logs neither. (Recommended for production systems.) 113 | 114 | [handler_console] 115 | class = StreamHandler 116 | args = (sys.stderr,) 117 | level = NOTSET 118 | formatter = generic 119 | 120 | [formatter_generic] 121 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 122 | -------------------------------------------------------------------------------- /production.ini: -------------------------------------------------------------------------------- 1 | ### 2 | # app configuration 3 | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html 4 | ### 5 | 6 | [app:main] 7 | use = egg:ccvpn 8 | 9 | pyramid.reload_templates = false 10 | pyramid.debug_authorization = false 11 | pyramid.debug_notfound = false 12 | pyramid.debug_routematch = false 13 | pyramid.default_locale_name = en 14 | 15 | sqlalchemy.url = sqlite:///%(here)s/ccvpn.sqlite 16 | 17 | session.type = file 18 | session.data_dir = %(here)s/.tmp/session_data 19 | session.lock_dir = %(here)s/.tmp/session_lock 20 | session.key = session 21 | session.cookie_on_exception = false 22 | 23 | mail.host = localhost 24 | mail.port = 25 25 | mail.default_sender = support@ccrypto.org 26 | 27 | # Put a HTTPS link when using HTTP ? 28 | use_https = true 29 | # HTTPS port to use (default: 443) 30 | #https_port = 443 31 | 32 | # OpenVPN gateway public address 33 | openvpn.gateway = gw.vpn.ccrypto.org 34 | 35 | # OpenVPN CA certificate to be embedded into .ovpn files 36 | openvpn.ca-cert = %(here)s/ca.crt 37 | 38 | # Bitcoin settings 39 | # empty user = connect to local daemon 40 | bitcoin.user = 41 | bitcoin.password = 42 | bitcoin.host = localhost 43 | bitcoin.port = 8332 44 | bitcoin.account = ccvpn2 45 | bitcoin.month_price = 0.2 46 | 47 | # Paypal settings 48 | paypal.title = CCrypto VPN 49 | #paypal.header_image = 50 | paypal.currency = EUR 51 | paypal.address = paypal@ccrypto.org 52 | 53 | # paypal.receiver will be used to check address when notified 54 | # if you use a secondary address for paypal.address, put your primary here. 55 | paypal.receiver = paypal@ccrypto.org 56 | 57 | # Paypal sandbox API 58 | paypal.test = true 59 | 60 | [alembic] 61 | script_location = alembic/ 62 | pylons_config_file = ./production.ini 63 | 64 | [pshell] 65 | m = ccvpn.models 66 | dbs = ccvpn.models.DBSession 67 | 68 | [server:main] 69 | use = egg:waitress#main 70 | host = 0.0.0.0 71 | port = 6543 72 | 73 | ### 74 | # logging configuration 75 | # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html 76 | ### 77 | 78 | [loggers] 79 | keys = root, ccvpn, sqlalchemy 80 | 81 | [handlers] 82 | keys = console 83 | 84 | [formatters] 85 | keys = generic 86 | 87 | [logger_root] 88 | level = WARN 89 | handlers = console 90 | 91 | [logger_ccvpn] 92 | level = WARN 93 | handlers = 94 | qualname = ccvpn 95 | 96 | [logger_sqlalchemy] 97 | level = WARN 98 | handlers = 99 | qualname = sqlalchemy.engine 100 | # "level = INFO" logs SQL queries. 101 | # "level = DEBUG" logs SQL queries and results. 102 | # "level = WARN" logs neither. (Recommended for production systems.) 103 | 104 | [handler_console] 105 | class = StreamHandler 106 | args = (sys.stderr,) 107 | level = NOTSET 108 | formatter = generic 109 | 110 | [formatter_generic] 111 | format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s 112 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | match=^test 3 | nocapture=1 4 | cover-package=ccvpn 5 | with-coverage=1 6 | cover-erase=1 7 | 8 | [compile_catalog] 9 | directory = ccvpn/locale 10 | domain = ccvpn 11 | statistics = true 12 | 13 | [extract_messages] 14 | add_comments = TRANSLATORS: 15 | output_file = ccvpn/locale/ccvpn.pot 16 | width = 80 17 | 18 | [init_catalog] 19 | domain = ccvpn 20 | input_file = ccvpn/locale/ccvpn.pot 21 | output_dir = ccvpn/locale 22 | 23 | [update_catalog] 24 | domain = ccvpn 25 | input_file = ccvpn/locale/ccvpn.pot 26 | output_dir = ccvpn/locale 27 | previous = true 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | 4 | from setuptools import setup, find_packages 5 | 6 | here = os.path.abspath(os.path.dirname(__file__)) 7 | README = open(os.path.join(here, 'README.md')).read() 8 | 9 | requires = [ 10 | 'pyramid', 11 | 'SQLAlchemy', 12 | 'transaction', 13 | 'pyramid_tm', 14 | 'pyramid_debugtoolbar', 15 | 'pyramid_beaker', 16 | 'pyramid_mako', 17 | 'pyramid_mailer', 18 | 'zope.sqlalchemy', 19 | 'waitress', 20 | 'markdown', 21 | 'bitcoin-python', 22 | 'webtest', 23 | 'requests', 24 | 'python-dateutil', 25 | 'stripe', 26 | 'pygments', 27 | 'Babel', 28 | 'lingua', 29 | ] 30 | 31 | setup(name='ccvpn', 32 | version='0.0', 33 | description='ccvpn', 34 | long_description=README, 35 | classifiers=[ 36 | "Programming Language :: Python", 37 | "Framework :: Pyramid", 38 | "Topic :: Internet :: WWW/HTTP", 39 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", 40 | ], 41 | author='', 42 | author_email='', 43 | url='', 44 | keywords='web wsgi bfg pylons pyramid', 45 | packages=find_packages(), 46 | include_package_data=True, 47 | zip_safe=False, 48 | test_suite='ccvpn', 49 | install_requires=requires, 50 | entry_points="""\ 51 | [paste.app_factory] 52 | main = ccvpn:main 53 | [console_scripts] 54 | initialize_ccvpn_db = ccvpn.scripts.initializedb:main 55 | ccvpn_checkbtcorders = ccvpn.scripts.checkbtcorders:main 56 | ccvpn_apiacl = ccvpn.scripts.apiacl:main 57 | ccvpn_mail = ccvpn.scripts.mail:main 58 | ccvpn_expire_mail = ccvpn.scripts.expire_mail:main 59 | """, 60 | message_extractors = { '.': [ 61 | ('**.py', 'python', None ), 62 | ('**.mako', 'mako', None ), 63 | ]}, 64 | ) 65 | --------------------------------------------------------------------------------