Esempio n. 1
0
 def handle_api_list_installed(self, http_context):
     r = []
     manager = PluginManager.get(aj.context)
     for name in manager:
         plugin = manager[name]
         r.append({
             'name':
             plugin['info']['name'],
             'imported':
             plugin['imported'],
             'crash':
             self.__serialize_exception(
                 manager.get_crash(plugin['info']['name'])),
             'path':
             plugin['path'],
             'author':
             plugin['info']['author'],
             'author_email':
             plugin['info']['email'],
             'url':
             plugin['info']['url'],
             'icon':
             plugin['info']['icon'],
             'version':
             plugin['info']['version'],
             'title':
             plugin['info']['title'],
         })
     return r
Esempio n. 2
0
    def handle_view(self, http_context):
        if aj.dev:
            rebuild_all = http_context.env.get("HTTP_CACHE_CONTROL", None) == "no-cache"

            for provider in aj.plugin_providers:
                if isinstance(provider, DirectoryPluginProvider):
                    logging.debug("Building resources in %s", provider.path)
                    if rebuild_all:
                        cmd = ["ajenti-dev-multitool", "--rebuild"]
                    else:
                        cmd = ["ajenti-dev-multitool", "--build"]
                    p = subprocess.Popen(cmd, cwd=provider.path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                    o, e = p.communicate()
                    if p.returncode != 0:
                        logging.error("Resource compilation failed")
                        logging.error(o + e)

        manager = PluginManager.get(aj.context)
        path = manager.get_content_path("core", "content/pages/index.html")
        content = open(path).read() % {
            "prefix": http_context.prefix,
            "plugins": json.dumps(dict((manager[n]["info"]["name"], manager[n]["info"]["title"]) for n in manager)),
            "version": aj.version,
            "platform": aj.platform,
            "platformUnmapped": aj.platform_unmapped,
            "bootstrapColor": aj.config.data.get("color", None),
        }
        http_context.add_header("Content-Type", "text/html")
        http_context.respond_ok()
        return content
Esempio n. 3
0
    def handle_api_languages(self, http_context):
        mgr = PluginManager.get(aj.context)
        languages = set()
        for id in mgr:
            for lang in os.listdir(mgr.get_content_path(id, 'locale')):
                if lang != 'app.pot':
                    languages.add(lang)

        return sorted(list(languages))
Esempio n. 4
0
    def handle_api_languages(self, http_context):
        mgr = PluginManager.get(aj.context)
        languages = set()
        for id in mgr:
            for lang in os.listdir(mgr.get_content_path(id, 'locale')):
                if lang != 'app.pot':
                    languages.add(lang)

        return sorted(list(languages))
Esempio n. 5
0
 def get_augeas(self):
     return Augeas(
         modules=[{
             'name': 'Supervisor',
             'lens': 'Supervisor.lns',
             'incl': [
                 self.path,
             ]
         }],
         loadpath=PluginManager.get(aj.context).get_content_path('supervisor', ''),
     )
Esempio n. 6
0
def make_report(e):
    """
    Formats a bug report.
    """
    import platform as _platform
    from aj import platform, platform_unmapped, platform_string, version, debug

    tb = traceback.format_exc()
    tb = '\n'.join('    ' + x for x in tb.splitlines())

    import gevent
    import greenlet
    import psutil
    from aj.plugins import PluginManager

    return """Ajenti bug report
--------------------


Info | Value
----- | -----
Ajenti | %s
Platform | %s / %s / %s
Architecture | %s
Python | %s
Debug | %s
Loaded plugins | %s

Library | Version
------- | -------
gevent | %s
greenlet | %s
psutil | %s


%s

            """ % (
        version,
        platform,
        platform_unmapped,
        platform_string,
        subprocess.check_output(['uname', '-m']).strip().decode(),
        '.'.join(str(x) for x in _platform.python_version_tuple()),
        debug,
        ', '.join(
            sorted(PluginManager.get(aj.context).get_loaded_plugins_list()))
        if aj.context else 'None',
        gevent.__version__,
        greenlet.__version__,
        psutil.__version__,
        tb,
    )
Esempio n. 7
0
 def get_augeas(self):
     return Augeas(
         modules=[{
             'name': 'Supervisor',
             'lens': 'Supervisor.lns',
             'incl': [
                 self.path,
             ]
         }],
         loadpath=PluginManager.get(aj.context).get_content_path(
             'supervisor', ''),
     )
Esempio n. 8
0
def make_report(e):
    """
    Formats a bug report.
    """
    import platform as _platform
    from aj import platform, platform_unmapped, platform_string, version, debug

    tb = traceback.format_exc(e)
    tb = '\n'.join('    ' + x for x in tb.splitlines())


    import gevent
    import greenlet
    import psutil
    from aj.plugins import PluginManager

    return """Ajenti bug report
--------------------


Info | Value
----- | -----
Ajenti | %s
Platform | %s / %s / %s
Architecture | %s
Python | %s
Debug | %s
Loaded plugins | %s

Library | Version
------- | -------
gevent | %s
greenlet | %s
psutil | %s


%s

            """ % (
            version,
            platform, platform_unmapped, platform_string,
            subprocess.check_output(['uname', '-mp']).strip(),
            '.'.join([str(x) for x in _platform.python_version_tuple()]),
            debug,
            ', '.join(sorted(PluginManager.get(aj.context).get_loaded_plugins_list())),

            gevent.__version__,
            greenlet.__version__,
            psutil.__version__,

            tb,
        )
Esempio n. 9
0
    def handle_view(self, http_context):
        if aj.dev:
            restricted_user = aj.config.data['restricted_user']
            if os.getuid() != pwd.getpwnam(restricted_user).pw_uid:
                rebuild_all = http_context.env.get('HTTP_CACHE_CONTROL',
                                                   None) == 'no-cache'

                for provider in aj.plugin_providers:
                    if isinstance(provider, DirectoryPluginProvider):
                        logging.debug('Building resources in %s',
                                      provider.path)
                        if rebuild_all:
                            cmd = ['ajenti-dev-multitool', '--rebuild']
                        else:
                            cmd = ['ajenti-dev-multitool', '--build']
                        p = subprocess.Popen(cmd,
                                             cwd=provider.path,
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.PIPE)
                        o, e = p.communicate()
                        if p.returncode != 0:
                            logging.error('Resource compilation failed')
                            logging.error(o + e)
            else:
                logging.warning("Cannot build resources as restricted user %s",
                                restricted_user)

        manager = PluginManager.get(aj.context)
        path = manager.get_content_path('core', 'content/pages/index.html')
        content = open(path).read() % {
            'prefix':
            http_context.prefix,
            'plugins':
            json.dumps({
                manager[n]['info']['name']: manager[n]['info']['title']
                for n in manager
            }),
            'config':
            json.dumps(aj.config.data),
            'version':
            str(aj.version),
            'platform':
            aj.platform,
            'platformUnmapped':
            aj.platform_unmapped,
            'bootstrapColor':
            aj.config.data.get('color', None),
        }
        http_context.add_header('Content-Type', 'text/html')
        http_context.respond_ok()
        return content
Esempio n. 10
0
 def handle_api_list_installed(self, http_context):
     r = []
     for plugin in PluginManager.get(aj.context).get_all().values():
         r.append({
             'name': plugin.name,
             'active': plugin.active,
             'crash': plugin.crash,
             'path': plugin.path,
             'author': plugin.author,
             'author_email': plugin.email,
             'url': plugin.url,
             'icon': plugin.icon,
             'version': plugin.version,
             'title': plugin.title,
         })
     return r
Esempio n. 11
0
    def handle_file(self, http_context, plugin=None, path=None):
        """
        Connector to get a specific file from plugin.

        :param http_context: HttpContext
        :type http_context: HttpContext
        :param plugin: Plugin name
        :type plugin: string
        :param path: Path of the file
        :type path: string
        :return: Compressed content of the file
        :rtype: gzip
        """

        if '..' in path:
            return http_context.respond_not_found()
        return http_context.file(
            PluginManager.get(aj.context).get_content_path(plugin, path))
Esempio n. 12
0
 def handle_api_list_installed(self, http_context):
     r = []
     manager = PluginManager.get(aj.context)
     for name in manager:
         plugin = manager[name]
         r.append({
             'name': plugin['info']['name'],
             'imported': plugin['imported'],
             'crash': self.__serialize_exception(manager.get_crash(plugin['info']['name'])),
             'path': plugin['path'],
             'author': plugin['info']['author'],
             'author_email': plugin['info']['email'],
             'url': plugin['info']['url'],
             'icon': plugin['info']['icon'],
             'version': plugin['info']['version'],
             'title': plugin['info']['title'],
         })
     return r
Esempio n. 13
0
    def handle_api_languages(self, http_context):
        """
        List all availables languages.

        :param http_context: HttpContext
        :type http_context: HttpContext
        :return: List of languages
        :rtype: list
        """

        mgr = PluginManager.get(aj.context)
        languages = set()
        for id in mgr:
            locale_dir = mgr.get_content_path(id, 'locale')
            if os.path.isdir(locale_dir):
                for lang in os.listdir(locale_dir):
                    if lang != 'app.pot':
                        languages.add(lang)

        return sorted(list(languages))
Esempio n. 14
0
    def handle_api_list_installed(self, http_context):
        """
        Get the plugin list from PluginManager.

        :param http_context: HttpContext
        :type http_context: HttpContext
        :return: List of plugin informations, one plugin per dict
        :rtype: list of dict
        """

        r = []
        manager = PluginManager.get(aj.context)
        for name in manager:
            plugin = manager[name]
            r.append({
                'name':
                plugin['info']['name'],
                'imported':
                plugin['imported'],
                'crash':
                self.__serialize_exception(
                    manager.get_crash(plugin['info']['name'])),
                'path':
                plugin['path'],
                'author':
                plugin['info']['author'],
                'author_email':
                plugin['info']['email'],
                'url':
                plugin['info']['url'],
                'icon':
                plugin['info']['icon'],
                'version':
                plugin['info']['version'],
                'title':
                plugin['info']['title'],
                'documentation':
                plugin['info'].get('docs', ''),
            })
        return r
Esempio n. 15
0
def make_report(e):
    """
    Formats a bug report.
    """
    import platform as _platform
    from aj import platform, platform_unmapped, platform_string, version, debug

    tb = traceback.format_exc()
    tb = '\n'.join('    ' + x for x in tb.splitlines())

    import gevent
    import greenlet
    import psutil
    from aj.plugins import PluginManager

    architecture = subprocess.check_output(['uname', '-m']).strip().decode()
    python = '.'.join(str(x) for x in _platform.python_version_tuple())
    plugins = ', '.join(
        sorted(PluginManager.get(
            aj.context).get_loaded_plugins_list())) if aj.context else 'None'

    return f"""Ajenti bug report
Esempio n. 16
0
    def handle_view(self, http_context):
        if aj.dev:
            rebuild_all = http_context.env.get('HTTP_CACHE_CONTROL', None) == 'no-cache'

            for provider in aj.plugin_providers:
                if isinstance(provider, DirectoryPluginProvider):
                    logging.debug('Building resources in %s', provider.path)
                    if rebuild_all:
                        cmd = ['ajenti-dev-multitool', '--rebuild']
                    else:
                        cmd = ['ajenti-dev-multitool', '--build']
                    p = subprocess.Popen(
                        cmd,
                        cwd=provider.path,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE
                    )
                    o, e = p.communicate()
                    if p.returncode != 0:
                        logging.error('Resource compilation failed')
                        logging.error(o + e)

        manager = PluginManager.get(aj.context)
        path = manager.get_content_path('core', 'content/pages/index.html')
        content = open(path).read() % {
            'prefix': http_context.prefix,
            'plugins': json.dumps(
                dict((manager[n]['info']['name'], manager[n]['info']['title']) for n in manager)
            ),
            'config': json.dumps(aj.config.data),
            'version': aj.version,
            'platform': aj.platform,
            'platformUnmapped': aj.platform_unmapped,
            'bootstrapColor': aj.config.data.get('color', None),
        }
        http_context.add_header('Content-Type', 'text/html')
        http_context.respond_ok()
        return content
Esempio n. 17
0
    def handle_view(self, http_context):
        """
        Catch all for all not handled url. Generate an appropriate page and reload all
        extern resources if ajenti is used in dev mode.
        This function is also called when a page is refreshed through F5.

        :param http_context: HttpContext
        :type http_context: HttpContext
        :return: Page content
        :rtype: string
        """

        if aj.dev:
            restricted_user = aj.config.data['restricted_user']
            if os.getuid() != pwd.getpwnam(restricted_user).pw_uid:
                rebuild_all = http_context.env.get('HTTP_CACHE_CONTROL',
                                                   None) == 'no-cache'

                for provider in aj.plugin_providers:
                    if isinstance(provider, DirectoryPluginProvider):
                        logging.debug('Building resources in %s',
                                      provider.path)
                        if rebuild_all:
                            cmd = ['ajenti-dev-multitool', '--rebuild']
                        else:
                            cmd = ['ajenti-dev-multitool', '--build']
                        p = subprocess.Popen(cmd,
                                             cwd=provider.path,
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.PIPE)
                        o, e = p.communicate()
                        if p.returncode != 0:
                            logging.error('Resource compilation failed')
                            logging.error((o + e).decode('utf-8'))
            else:
                logging.warning("Cannot build resources as restricted user %s",
                                restricted_user)

        manager = PluginManager.get(aj.context)
        path = manager.get_content_path('core', 'content/pages/index.html')
        content = open(path).read() % {
            'prefix':
            http_context.prefix,
            'plugins':
            json.dumps({
                manager[n]['info']['name']: manager[n]['info']['title']
                for n in manager
            }),
            'config':
            json.dumps(aj.config.get_non_sensitive_data()),
            'version':
            str(aj.version),
            'platform':
            aj.platform,
            'platformUnmapped':
            aj.platform_unmapped,
            'devMode':
            aj.dev,
            'bootstrapColor':
            aj.config.data.get('color', None),
        }
        http_context.add_header('Content-Type', 'text/html')
        http_context.respond_ok()
        return content
Esempio n. 18
0
def run(config=None, plugin_providers=None, product_name='ajenti', dev_mode=False,
        debug_mode=False, autologin=False):
    """
    A global entry point for Ajenti.

    :param config: config file implementation instance to use
    :type  config: :class:`aj.config.BaseConfig`
    :param plugin_providers: list of plugin providers to load plugins from
    :type  plugin_providers: list(:class:`aj.plugins.PluginProvider`)
    :param str product_name: a product name to use
    :param bool dev_mode: enables dev mode (automatic resource recompilation)
    :param bool debug_mode: enables debug mode (verbose and extra logging)
    :param bool autologin: disables authentication and logs everyone in as the user running the panel. This is EXTREMELY INSECURE.
    """
    if config is None:
        raise TypeError('`config` can\'t be None')

    reload(sys)
    if hasattr(sys, 'setdefaultencoding'):
        sys.setdefaultencoding('utf8')

    aj.product = product_name
    aj.debug = debug_mode
    aj.dev = dev_mode
    aj.dev_autologin = autologin

    aj.init()
    aj.log.set_log_params(tag='master', master_pid=os.getpid())
    aj.context = Context()
    aj.config = config
    aj.plugin_providers = plugin_providers or []
    logging.info('Loading config from %s', aj.config)
    aj.config.load()
    aj.config.ensure_structure()

    if aj.debug:
        logging.warning('Debug mode')
    if aj.dev:
        logging.warning('Dev mode')

    try:
        locale.setlocale(locale.LC_ALL, '')
    except locale.Error:
        logging.warning('Couldn\'t set default locale')

    # install a passthrough gettext replacement since all localization is handled in frontend
    # and _() is here only for string extraction
    __builtins__['_'] = lambda x: x

    logging.info('Ajenti Core %s', aj.version)
    logging.info('Master PID - %s', os.getpid())
    logging.info('Detected platform: %s / %s', aj.platform, aj.platform_string)
    logging.info('Python version: %s', aj.python_version)

    # Load plugins
    PluginManager.get(aj.context).load_all_from(aj.plugin_providers)
    if len(PluginManager.get(aj.context)) == 0:
        logging.warning('No plugins were loaded!')

    if aj.config.data['bind']['mode'] == 'unix':
        path = aj.config.data['bind']['socket']
        if os.path.exists(path):
            os.unlink(path)
        listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            listener.bind(path)
        except OSError:
            logging.error('Could not bind to %s', path)
            sys.exit(1)

    if aj.config.data['bind']['mode'] == 'tcp':
        host = aj.config.data['bind']['host']
        port = aj.config.data['bind']['port']
        listener = socket.socket(
            socket.AF_INET6 if ':' in host else socket.AF_INET, socket.SOCK_STREAM
        )
        if aj.platform not in ['freebsd', 'osx']:
            try:
                listener.setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)
            except socket.error:
                logging.warning('Could not set TCP_CORK')
        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        logging.info('Binding to [%s]:%s', host, port)
        try:
            listener.bind((host, port))
        except socket.error as e:
            logging.error('Could not bind: %s', str(e))
            sys.exit(1)

    # Fix stupid socketio bug (it tries to do *args[0][0])
    socket.socket.__getitem__ = lambda x, y: None

    listener.listen(10)

    gateway = GateMiddleware.get(aj.context)
    application = HttpRoot(HttpMiddlewareAggregator([gateway])).dispatch

    aj.server = SocketIOServer(
        listener,
        log=open(os.devnull, 'w'),
        application=application,
        handler_class=RequestHandler,
        policy_server=False,
        transports=[
            str('websocket'),
            str('xhr-polling'),
            str('jsonp-polling'),
        ],
    )

    if aj.config.data['ssl']['enable'] and aj.config.data['bind']['mode'] == 'tcp':
        aj.server.ssl_args = {'server_side': True}
        cert_path = aj.config.data['ssl']['certificate']
        if aj.config.data['ssl']['fqdn_certificate']:
            fqdn_cert_path = aj.config.data['ssl']['fqdn_certificate']
        else:
            fqdn_cert_path = cert_path

        context = gevent.ssl.SSLContext(ssl.PROTOCOL_TLS)
        context.load_cert_chain(certfile=fqdn_cert_path, keyfile=fqdn_cert_path)
        context.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
        context.set_ciphers('ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!RC4:+HIGH:+MEDIUM')

        if aj.config.data['ssl']['client_auth']['enable']:

            logging.info('Enabling SSL client authentication')
            context.load_verify_locations(cafile=cert_path)
            if aj.config.data['ssl']['client_auth']['force']:
                context.verify_mode = ssl.CERT_REQUIRED
            else:
                context.verify_mode = ssl.CERT_OPTIONAL

            ## Test callback : client_certificate_callback must return None to get forward
            # context.set_servername_callback(AuthenticationService.get(aj.context).client_certificate_callback)

        aj.server.wrap_socket = lambda socket, **args:context.wrap_socket(sock=socket, server_side=True)
        logging.info('SSL enabled')

    # auth.log
    try:
        syslog.openlog(
            ident=str(aj.product),
            facility=syslog.LOG_AUTH,
        )
    except Exception as e:
        syslog.openlog(aj.product)

    def cleanup():
        if hasattr(cleanup, 'started'):
            return
        cleanup.started = True
        logging.info('Process %s exiting normally', os.getpid())
        gevent.signal(signal.SIGINT, lambda: None)
        gevent.signal(signal.SIGTERM, lambda: None)
        if aj.master:
            gateway.destroy()

        p = psutil.Process(os.getpid())
        for c in p.children(recursive=True):
            try:
                os.killpg(c.pid, signal.SIGTERM)
                os.killpg(c.pid, signal.SIGKILL)
            except OSError:
                pass

    def signal_handler():
        cleanup()
        sys.exit(0)

    gevent.signal(signal.SIGINT, signal_handler)
    gevent.signal(signal.SIGTERM, signal_handler)

    aj.server.serve_forever()

    if not aj.master:
        # child process, server is stopped, wait until killed
        gevent.wait()

    if hasattr(aj.server, 'restart_marker'):
        logging.warning('Restarting by request')
        cleanup()

        fd = 20  # Close all descriptors. Creepy thing
        while fd > 2:
            try:
                os.close(fd)
                logging.debug('Closed descriptor #%i', fd)
            except OSError:
                pass
            fd -= 1

        logging.warning('Will restart the process now')
        if '-d' in sys.argv:
            sys.argv.remove('-d')
        os.execv(sys.argv[0], sys.argv)
    else:
        if aj.master:
            logging.debug('Server stopped')
            cleanup()
Esempio n. 19
0
 def __init__(self, http_context):
     self.cache = {}
     self.use_cache = not aj.debug
     self.mgr = PluginManager.get(aj.context)
Esempio n. 20
0
 def handle_file(self, http_context, plugin=None, path=None):
     if '..' in path:
         return http_context.respond_not_found()
     return http_context.file(PluginManager.get(aj.context).get_content_path(plugin, path))
Esempio n. 21
0
 def handle_file(self, http_context, plugin=None, path=None):
     if '..' in path:
         return http_context.respond_not_found()
     return http_context.file(
         PluginManager.get(aj.context).get_content_path(plugin, path))
Esempio n. 22
0
 def __init__(self, http_context):
     self.cache = {}
     self.use_cache = not aj.debug
     self.mgr = PluginManager.get(aj.context)
Esempio n. 23
0
	def getPluginPath(self):
		manager = PluginManager.get(aj.context)
		plugin = manager['coach']
		return plugin['path']
Esempio n. 24
0
def run(config=None,
        plugin_providers=None,
        product_name='ajenti',
        dev_mode=False,
        debug_mode=False,
        autologin=False):
    """
    A global entry point for Ajenti.

    :param config: config file implementation instance to use
    :type  config: :class:`aj.config.BaseConfig`
    :param plugin_providers: list of plugin providers to load plugins from
    :type  plugin_providers: list(:class:`aj.plugins.PluginProvider`)
    :param str product_name: a product name to use
    :param bool dev_mode: enables dev mode (automatic resource recompilation)
    :param bool debug_mode: enables debug mode (verbose and extra logging)
    :param bool autologin: disables authentication and logs everyone in as the user running the panel. This is EXTREMELY INSECURE.
    """
    if config is None:
        raise TypeError('`config` can\'t be None')

    reload(sys)
    if hasattr(sys, 'setdefaultencoding'):
        sys.setdefaultencoding('utf8')

    aj.product = product_name
    aj.debug = debug_mode
    aj.dev = dev_mode
    aj.dev_autologin = autologin

    aj.init()
    aj.log.set_log_params(tag='master', master_pid=os.getpid())
    aj.context = Context()
    aj.config = config
    aj.plugin_providers = plugin_providers or []
    logging.info(f'Loading config from {aj.config}')
    aj.config.load()
    aj.config.ensure_structure()

    logging.info('Loading users from /etc/ajenti/users.yml')
    aj.users = AjentiUsers(aj.config.data['auth']['users_file'])
    aj.users.load()

    logging.info('Loading smtp config from /etc/ajenti/smtp.yml')
    aj.smtp_config = SmtpConfig()
    aj.smtp_config.load()
    aj.smtp_config.ensure_structure()

    if aj.debug:
        logging.warning('Debug mode')
    if aj.dev:
        logging.warning('Dev mode')

    try:
        locale.setlocale(locale.LC_ALL, '')
    except locale.Error:
        logging.warning('Couldn\'t set default locale')

    # install a passthrough gettext replacement since all localization is handled in frontend
    # and _() is here only for string extraction
    __builtins__['_'] = lambda x: x

    logging.info(f'Ajenti Core {aj.version}')
    logging.info(f'Master PID - {os.getpid()}')
    logging.info(f'Detected platform: {aj.platform} / {aj.platform_string}')
    logging.info(f'Python version: {aj.python_version}')

    # Load plugins
    PluginManager.get(aj.context).load_all_from(aj.plugin_providers)
    if len(PluginManager.get(aj.context)) == 0:
        logging.warning('No plugins were loaded!')

    if aj.config.data['bind']['mode'] == 'unix':
        path = aj.config.data['bind']['socket']
        if os.path.exists(path):
            os.unlink(path)
        listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            listener.bind(path)
        except OSError:
            logging.error(f'Could not bind to {path}')
            sys.exit(1)

    if aj.config.data['bind']['mode'] == 'tcp':
        host = aj.config.data['bind']['host']
        port = aj.config.data['bind']['port']
        listener = socket.socket(
            socket.AF_INET6 if ':' in host else socket.AF_INET,
            socket.SOCK_STREAM)
        if aj.platform not in ['freebsd', 'osx']:
            try:
                listener.setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)
            except socket.error:
                logging.warning('Could not set TCP_CORK')
        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        logging.info(f'Binding to [{host}]:{port}')
        try:
            listener.bind((host, port))
        except socket.error as e:
            logging.error(f'Could not bind: {str(e)}')
            sys.exit(1)

    listener.listen(10)

    gateway = GateMiddleware.get(aj.context)
    middleware_stack = HttpMasterMiddleware.all(aj.context) + [gateway]

    sio = Server(async_mode='gevent',
                 cors_allowed_origins=aj.config.data['trusted_domains'])
    application = WSGIApp(
        sio,
        HttpRoot(HttpMiddlewareAggregator(middleware_stack)).dispatch)
    sio.register_namespace(SocketIONamespace(context=aj.context))

    if aj.config.data['ssl']['enable'] and aj.config.data['bind'][
            'mode'] == 'tcp':
        aj.server = pywsgi.WSGIServer(
            listener,
            log=open(os.devnull, 'w'),
            application=application,
            handler_class=RequestHandler,
            policy_server=False,
        )
        aj.server.ssl_args = {'server_side': True}
        cert_path = aj.config.data['ssl']['certificate']
        if aj.config.data['ssl']['fqdn_certificate']:
            fqdn_cert_path = aj.config.data['ssl']['fqdn_certificate']
        else:
            fqdn_cert_path = cert_path

        context = gevent.ssl.SSLContext(ssl.PROTOCOL_TLS)
        context.load_cert_chain(certfile=fqdn_cert_path,
                                keyfile=fqdn_cert_path)
        context.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
        context.set_ciphers(
            'ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!RC4:+HIGH:+MEDIUM')

        if aj.config.data['ssl']['client_auth']['enable']:

            logging.info('Enabling SSL client authentication')
            context.load_verify_locations(cafile=cert_path)
            if aj.config.data['ssl']['client_auth']['force']:
                context.verify_mode = ssl.CERT_REQUIRED
            else:
                context.verify_mode = ssl.CERT_OPTIONAL

            ## Test callback : client_certificate_callback must return None to get forward
            # context.set_servername_callback(AuthenticationService.get(aj.context).client_certificate_callback)

        aj.server.wrap_socket = lambda socket, **args: context.wrap_socket(
            sock=socket, server_side=True)
        logging.info('SSL enabled')

        if aj.config.data['ssl']['force']:
            from aj.https_redirect import http_to_https
            target_url = f'https://{aj.config.data["name"]}:{port}'
            gevent.spawn(http_to_https, target_url)
            logging.info(
                f'HTTP to HTTPS redirection activated to {target_url}')
    else:
        # No policy_server argument for http
        aj.server = pywsgi.WSGIServer(
            listener,
            log=open(os.devnull, 'w'),
            application=application,
            handler_class=RequestHandler,
        )

    # auth.log
    try:
        syslog.openlog(
            ident=str(aj.product),
            facility=syslog.LOG_AUTH,
        )
    except Exception as e:
        syslog.openlog(aj.product)

    def cleanup():
        if hasattr(cleanup, 'started'):
            return
        cleanup.started = True
        logging.info(f'Process {os.getpid()} exiting normally')
        gevent.signal_handler(signal.SIGINT, lambda: None)
        gevent.signal_handler(signal.SIGTERM, lambda: None)
        if aj.master:
            gateway.destroy()

        p = psutil.Process(os.getpid())
        for c in p.children(recursive=True):
            try:
                os.killpg(c.pid, signal.SIGTERM)
                os.killpg(c.pid, signal.SIGKILL)
            except OSError:
                pass

    def signal_handler():
        cleanup()
        sys.exit(0)

    gevent.signal_handler(signal.SIGINT, signal_handler)
    gevent.signal_handler(signal.SIGTERM, signal_handler)

    aj.server.serve_forever()

    if not aj.master:
        # child process, server is stopped, wait until killed
        gevent.wait()

    if hasattr(aj.server, 'restart_marker'):
        logging.warning('Restarting by request')
        cleanup()

        fd = 20  # Close all descriptors. Creepy thing
        while fd > 2:
            try:
                os.close(fd)
                logging.debug(f'Closed descriptor #{fd:d}')
            except OSError:
                pass
            fd -= 1

        restart()
    else:
        if aj.master:
            logging.debug('Server stopped')
            cleanup()
Esempio n. 25
0
def run(config=None, plugin_providers=None, product_name='ajenti', dev_mode=False,
        debug_mode=False, autologin=False):
    """
    A global entry point for Ajenti.

    :param config: config file implementation instance to use
    :type  config: :class:`aj.config.BaseConfig`
    :param plugin_providers: list of plugin providers to load plugins from
    :type  plugin_providers: list(:class:`aj.plugins.PluginProvider`)
    :param str product_name: a product name to use
    :param bool dev_mode: enables dev mode (automatic resource recompilation)
    :param bool debug_mode: enables debug mode (verbose and extra logging)
    :param bool autologin: disables authentication and logs everyone in as the user running the panel. This is EXTREMELY INSECURE.
    """
    if config is None:
        raise TypeError('`config` can\'t be None')

    reload(sys)
    sys.setdefaultencoding('utf8')

    aj.product = product_name
    aj.debug = debug_mode
    aj.dev = dev_mode
    aj.dev_autologin = autologin

    aj.init()
    aj.log.set_log_params(tag='master', master_pid=os.getpid())
    aj.context = Context()
    aj.config = config
    aj.plugin_providers = plugin_providers or []
    logging.info('Loading config from %s', aj.config)
    aj.config.load()
    aj.config.ensure_structure()

    if aj.debug:
        logging.warn('Debug mode')
    if aj.dev:
        logging.warn('Dev mode')

    try:
        locale.setlocale(locale.LC_ALL, '')
    except locale.Error:
        logging.warning('Couldn\'t set default locale')

    # install a passthrough gettext replacement since all localization is handled in frontend
    # and _() is here only for string extraction
    __builtins__['_'] = lambda x: x

    logging.info('Ajenti Core %s', aj.version)
    logging.info('Detected platform: %s / %s', aj.platform, aj.platform_string)

    # Load plugins
    PluginManager.get(aj.context).load_all_from(aj.plugin_providers)
    if len(PluginManager.get(aj.context)) == 0:
        logging.warn('No plugins were loaded!')

    if aj.config.data['bind']['mode'] == 'unix':
        path = aj.config.data['bind']['socket']
        if os.path.exists(path):
            os.unlink(path)
        listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            listener.bind(path)
        except OSError:
            logging.error('Could not bind to %s', path)
            sys.exit(1)

    if aj.config.data['bind']['mode'] == 'tcp':
        host = aj.config.data['bind']['host']
        port = aj.config.data['bind']['port']
        listener = socket.socket(
            socket.AF_INET6 if ':' in host else socket.AF_INET, socket.SOCK_STREAM
        )
        if aj.platform not in ['freebsd', 'osx']:
            try:
                listener.setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1)
            except socket.error:
                logging.warn('Could not set TCP_CORK')
        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        logging.info('Binding to [%s]:%s', host, port)
        try:
            listener.bind((host, port))
        except socket.error as e:
            logging.error('Could not bind: %s', str(e))
            sys.exit(1)

    # Fix stupid socketio bug (it tries to do *args[0][0])
    socket.socket.__getitem__ = lambda x, y: None

    listener.listen(10)

    gateway = GateMiddleware.get(aj.context)
    application = HttpRoot(HttpMiddlewareAggregator([gateway])).dispatch

    aj.server = SocketIOServer(
        listener,
        log=open(os.devnull, 'w'),
        application=application,
        handler_class=RequestHandler,
        policy_server=False,
        transports=[
            str('websocket'),
            str('flashsocket'),
            str('xhr-polling'),
            str('jsonp-polling'),
        ],
    )

    if aj.config.data['ssl']['enable'] and aj.config.data['bind']['mode'] == 'tcp':
        try:
            context = SSL.Context(SSL.TLSv1_2_METHOD)
        except:
            context = SSL.Context(SSL.TLSv1_METHOD)
        context.set_session_id(str(id(context)))
        context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
        context.set_cipher_list('ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!RC4:+HIGH:+MEDIUM')

        certificate = crypto.load_certificate(
            crypto.FILETYPE_PEM,
            open(aj.config.data['ssl']['certificate']).read()
        )
        private_key = crypto.load_privatekey(
            crypto.FILETYPE_PEM,
            open(aj.config.data['ssl']['certificate']).read()
        )

        context.use_certificate(certificate)
        context.use_privatekey(private_key)

        if aj.config.data['ssl']['client_auth']['enable']:
            # todo harden files
            logging.info('Enabling SSL client authentication')
            context.add_client_ca(certificate)
            context.get_cert_store().add_cert(certificate)
            verify_flags = SSL.VERIFY_PEER
            if aj.config.data['ssl']['client_auth']['force']:
                verify_flags |= SSL.VERIFY_FAIL_IF_NO_PEER_CERT
            context.set_verify(verify_flags, AuthenticationService.get(aj.context).client_certificate_callback)
            context.set_verify_depth(0)

        aj.server.ssl_args = {'server_side': True}
        aj.server.wrap_socket = lambda socket, **ssl: SSLSocket(context, socket)
        logging.info('SSL enabled')

    # auth.log
    try:
        syslog.openlog(
            ident=str(aj.product),
            facility=syslog.LOG_AUTH,
        )
    except:
        syslog.openlog(aj.product)

    def cleanup():
        if hasattr(cleanup, 'started'):
            return
        cleanup.started = True
        logging.info('Process %s exiting normally', os.getpid())
        gevent.signal(signal.SIGINT, lambda: None)
        gevent.signal(signal.SIGTERM, lambda: None)
        if aj.master:
            gateway.destroy()

        p = psutil.Process(os.getpid())
        for c in p.children(recursive=True):
            try:
                os.killpg(c.pid, signal.SIGTERM)
                os.killpg(c.pid, signal.SIGKILL)
            except OSError:
                pass

    def signal_handler():
        cleanup()
        sys.exit(0)

    gevent.signal(signal.SIGINT, signal_handler)
    gevent.signal(signal.SIGTERM, signal_handler)

    aj.server.serve_forever()

    if not aj.master:
        # child process, server is stopped, wait until killed
        gevent.wait()

    if hasattr(aj.server, 'restart_marker'):
        logging.warn('Restarting by request')
        cleanup()

        fd = 20  # Close all descriptors. Creepy thing
        while fd > 2:
            try:
                os.close(fd)
                logging.debug('Closed descriptor #%i', fd)
            except OSError:
                pass
            fd -= 1

        logging.warn('Will restart the process now')
        if '-d' in sys.argv:
            sys.argv.remove('-d')
        os.execv(sys.argv[0], sys.argv)
    else:
        if aj.master:
            logging.debug('Server stopped')
            cleanup()