def get_accounts(id=None, status=False):
    result = []
    if id:
        data = Account.query.filter_by(id=id)
    else:
        data = Account.query.all()

    if data:
        accounts = [{'id': i.id, 'name': i.name,
                     'password': i.password, 'created_at': i.created_at.strftime('%Y-%m-%d %H:%M:%S')}
                    for i in data]
        if status:
            vpn = VpnServer()
            for account in accounts:
                statuses = vpn.account_status(account['name'])
                if statuses:
                    for status in statuses:
                        account['rip'] = status['rip'].split(':')[0]
                        account['vip'] = status['vip']
                        account['br'] = status['br']
                        account['bs'] = status['bs']
                        account['ct'] = datetime.fromtimestamp(int(status['ct'])).strftime('%Y-%m-%d %H:%M:%S')
                        result.append(copy.deepcopy(account))
                else:
                    result.append(copy.deepcopy(account))
        return sorted(result or accounts, key=lambda x: x.get('rip'), reverse=True)
    return None def account_update(form, id=None):
    account = VpnConfig()
    if account.update_account(id, form.name.data, form.password.data):
        return True
    return False def account_del(id):
    config = VpnConfig()
    vpn = VpnServer()
    if config.delete(id) and vpn.reload:
        return True
    return False class VpnServer(object):
    """vpn server console"""
    log_file = '/etc/openvpn/openvpn-status.log'
    pid_file = '/var/run/openvpn/server.pid'

    def __init__(self):
        self.cmd = None
        self.c_code = None
        self.c_stdout = None
        self.c_stderr = None

    def __repr__(self):
        return '<VpnServer %s:%s:%s:%s>' % (self.cmd, self.c_code,
                                            self.c_stdout, self.c_stderr)

    def _exec(self, cmd, message=None):
        try:
            r = exec_command(cmd)
        except:
            current_app.logger.error('[Dial Services]: exec_command error: %s:%s', cmd,
                                     sys.exc_info()[1])
            flash(u'VPN 程序异常，无法调用，请排查操作系统相关设置！', 'alert')
            return False
        #: store cmd info
        self.cmd = cmd
        self.c_code = r['return_code']
        self.c_stdout = r['stdout']
        self.c_stderr = r['stderr']
        #: check return code
        if r['return_code'] == 0:
            return True
        if message:
            flash(message % r['stderr'], 'alert')
        current_app.logger.error('[Dial Services]: exec_command return: %s:%s:%s', cmd,
                                 r['return_code'], r['stderr'])
        return False

    def _reload_conf(self):
        cmd = ['service', 'openvpn', 'reload']
        message = u"VPN 服务重载失败！%s"
        return self._exec(cmd, message)

    def _package_client_conf(self):
        cmd = ['/usr/local/flexgw/scripts/packconfig', 'all']
        message = u"客户端配置文件打包失败！"
        return self._exec(cmd, message)

    @property
    def start(self):
        if self.status:
            flash(u'服务已经启动！', 'info')
            return False
        cmd = ['service', 'openvpn', 'start']
        message = u"VPN 服务启动失败！%s"
        return self._exec(cmd, message)

    @property
    def stop(self):
        if not self.status:
            flash(u'服务已经停止！', 'info')
            return False
        cmd = ['service', 'openvpn', 'stop']
        message = u"VPN 服务停止失败！%s"
        return self._exec(cmd, message)

    @property
    def reload(self):
        tunnel = VpnConfig()
        if not tunnel.commit():
            message = u'VPN 服务配置文件修改失败，请重试！'
            flash(message, 'alert')
            return False
        self._package_client_conf()
        if not self.status:
            flash(u'设置成功！VPN 服务未启动，请通过「VPN服务管理」启动VPN 服务。', 'alert')
            return False
        if self._reload_conf():
            return True
        return False

    @property
    def status(self):
        if not os.path.isfile(self.pid_file):
            return False

        try:
            with open(self.pid_file) as f:
                raw_data = f.readlines()
        except:
            current_app.logger.error('[Dial Services]: open pid file error: %s:%s',
                                     self.pid_file, sys.exc_info()[1])
            return False
        if not raw_data:
            return False
        pid = int(raw_data[0])
        cmd = ['kill', '-0', str(pid)]
        return self._exec(cmd)

    def account_status(self, account_name):
        result = []
        if not self.status:
            return False
        try:
            with open(self.log_file) as f:
                raw_data = f.readlines()
        except:
            current_app.logger.error('[Dial Services]: open status log file error: %s:%s',
                                     self.log_file, sys.exc_info()[1])
            return False
        for line in raw_data:
            if line.startswith('CLIENT_LIST,%s,' % account_name):
                data = line.split(',')
                result.append({'rip': '%s' % data[2], 'vip': '%s' % data[3],
                              'br': data[4], 'bs': data[5], 'ct': data[7]})
        return result or False

    def tunnel_traffic(self, tunnel_name):
        pass def settings_update(form):
    account = VpnConfig()
    vpn = VpnServer()
    if account.update_settings(form.ipool.data,
                               form.subnet.data,
                               form.c2c.data,
                               form.duplicate.data,
                               form.proto.data) and vpn.reload:
        return True
    return False class AddForm(Form):
    name = StringField(u'账号名',
                       validators=[DataRequired(message=u'这是一个必选项！'),
                                   Length(max=20, message=u'帐号最长为20个字符！'),
                                   Regexp(r'^[\w]+$', message=u"只可包含如下字符：数字、字母、下划线！")])
    password = StringField(u'密码',
                           validators=[DataRequired(message=u'这是一个必选项！'),
                                       Length(max=20, message=u'密码最长为20个字符！'),
                                       Regexp(r'^[\w]+$', message=u"只可包含如下字符：数字、字母、下划线！")])
    #: submit button
    save = SubmitField(u'保存')
    delete = SubmitField(u'删除') class SettingsForm(Form):
    ipool = StringField(u'虚拟IP 地址池',
                        validators=[DataRequired(message=u'这是一个必选项！'),
                                    IPool(message=u"无效的IP 地址池")])
    subnet = TextAreaField(u'子网网段',
                           validators=[DataRequired(message=u'这是一个必选项！'),
                                       SubNets(message=u"无效的子网")])
    c2c = SelectField(u'允许client 间通信',
                      choices=[('no', u'否'), ('yes', u'是')])
    duplicate = SelectField(u'允许单个账号同时在线',
                            choices=[('no', u'否'), ('yes', u'是')])
    proto = SelectField(u'通信协议',
                        choices=[('udp', u'UDP'), ('tcp', u'TCP')]) class ConsoleForm(Form):
    '''web console form'''
    #: submit button
    stop = SubmitField(u'关闭')
    start = SubmitField(u'启动')
    re_load = SubmitField(u'下发&重载') class Account(db.Model):
    '''dial name.'''
    __tablename__ = 'dial_account'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True, index=True)
    password = db.Column(db.String(80))
    created_at = db.Column(db.DateTime)

    def __init__(self, name, password, created_at=datetime.now()):
        self.name = name
        self.password = password
        self.created_at = created_at

    def __repr__(self):
        return '<Dial Account %s:%s>' % (self.name, self.created_at)

    def get_id(self):
        return unicode(self.id) class Settings(db.Model):
    """settings for dial or common settings."""
    __tablename__ = 'dial_settings'

    id = db.Column(db.Integer, primary_key=True)
    ipool = db.Column(db.String(80))
    subnet = db.Column(db.String(80))
    c2c = db.Column(db.Boolean)
    duplicate = db.Column(db.Boolean)
    proto = db.Column(db.String(80))

    def __init__(self, ipool, subnet, c2c, duplicate, proto):
        self.ipool = ipool
        self.subnet = subnet
        self.c2c = c2c
        self.duplicate = duplicate
        self.proto = proto

    def __repr__(self):
        return '<Settings %s>' % self.id
from flask import Blueprint, render_template
from flask import url_for, redirect
from flask import flash
from flask.ext.login import login_required
from website.vpn.dial.services import get_accounts, account_update, account_del
from website.vpn.dial.services import VpnServer, settings_update
from website.vpn.dial.forms import AddForm, SettingsForm, ConsoleForm
from website.vpn.dial.models import Account, Settings
# -*- coding: utf-8 -*-
"""
    website.vpn.dial.views
    ~~~~~~~~~~~~~~~~~~~~~~

    vpn dial views:
        /vpn/dial/settings
        /vpn/dial/add
        /vpn/dial/<int:id>
        /vpn/dial/<int:id>/settings
"""





dial = Blueprint('dial', __name__, url_prefix='/vpn/dial',
                 template_folder='templates',
                 static_folder='static')


@dial.route('/')
@login_required
def index():
    accounts = get_accounts(status=True)
    if not accounts:
        flash(u'目前没有任何VPN 配置，如有需要请添加。', 'info')
    return render_template('dial/index.html', accounts=accounts)


@dial.route('/add', methods=['GET', 'POST'])
@login_required
def add():
    settings = Settings.query.get(1)
    if not settings:
        flash(u'提示：请先进行「设置」再添加VPN 账号。', 'alert')
        return redirect(url_for('dial.settings'))
    form = AddForm()
    if form.validate_on_submit():
        if not Account.query.filter_by(name=form.name.data).first():
            if account_update(form):
                message = u'添加VPN 拨号账号成功！'
                flash(message, 'success')
                return redirect(url_for('dial.index'))
        else:
            message = u'该账号已经存在：%s' % form.name.data
            flash(message, 'alert')
    return render_template('dial/add.html', form=form)


@dial.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
    form = SettingsForm()

    settings = Settings.query.get(1)