#!/usr/bin/python3 # -*- mode: python -*- # # This file is part of Plinth. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # """ Configuration helper for Plint PageKite interface Unfortunately there is no python3 package for augeas yet """ # Disable warning about invalid module name # pylint: disable-msg=C0103 import argparse #import augeas from configobj import ConfigObj import os import subprocess #aug = augeas.Augeas() aug = "XX" class ConfigFileCache(dict): """Cache files to not open files more than once""" def __missing__(self, path): self[path] = ConfigObj(path) return self[path] filecache = ConfigFileCache() CONF_PATH = '/etc/pagekite.d' PATHS = { 'account': os.path.join(CONF_PATH, '10_account.rc'), 'frontends': os.path.join(CONF_PATH, '20_frontends.rc'), 'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'), 'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc', 'abort_not_configured'), 'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'), 'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'), 'http': os.path.join(CONF_PATH, '80_httpd.rc', 'service_on'), 'https': os.path.join(CONF_PATH, '443_https.rc', 'service_on'), 'ssh': os.path.join(CONF_PATH, '80_sshd.rc', 'service_on'), } _CONF_PATH = '/files/etc/pagekite.d' _PATHS = { 'kitename': os.path.join(CONF_PATH, '10_account.rc', 'kitename'), 'kitesecret': os.path.join(CONF_PATH, '10_account.rc', 'kitesecret'), 'abort_not_configured': os.path.join(CONF_PATH, '10_account.rc', 'abort_not_configured'), 'defaults': os.path.join(CONF_PATH, '20_frontends.rc', 'defaults'), 'frontend': os.path.join(CONF_PATH, '20_frontends.rc', 'frontend'), 'http': os.path.join(CONF_PATH, '80_httpd.rc', 'service_on'), 'https': os.path.join(CONF_PATH, '443_https.rc', 'service_on'), 'ssh': os.path.join(CONF_PATH, '80_sshd.rc', 'service_on'), } # service entries are tuples with [source, destination, secret] # this information will be used when enabling a service SERVICES = { 'http': ['http:*.@kitename', 'localhost:80', '@kitesecret'], 'https': ['https:*.@kitename', 'localhost:443', '@kitesecret'], 'ssh': ['raw/22:@kitename', 'localhost:22', '@kitesecret'], } def parse_arguments(): """Return parsed command line arguments as dictionary""" parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(dest='subcommand', help='Sub command') # Start PageKite subparsers.add_parser('start', help='Start PageKite service') # Stop PageKite subparsers.add_parser('stop', help='Stop PageKite service') # Get status subparsers.add_parser('get-status', help='Get whether PakeKite is enabled') # Set status set_status = subparsers.add_parser('set-status', help='Enable/disable PageKite') set_status.add_argument('enable', choices=['enable', 'disable']) # Get whether default pagekite.net frontend is enabled subparsers.add_parser('get-pagekitenet-frontend-status', help='Get whether pagekite.net frontend is enabled') # Enable/Disable using default pagekite.net frontend subparsers.add_parser('enable-pagekitenet-frontend', help='Enable using default pagekite.net frontend') subparsers.add_parser('disable-pagekitenet-frontend', help='Disable default pagekite.net frontend') # Get frontend subparsers.add_parser('get-frontend', help='Get pagekite frontend') # Set frontend set_frontend = subparsers.add_parser('set-frontend', help='Set pagekite frontend') set_frontend.add_argument('url', help='frontend url') # Get kite details subparsers.add_parser('get-kite', help='Get configured kite name and secret') # Set kite details set_kite = subparsers.add_parser('set-kite', help='Configure kite name and its secret') set_kite.add_argument('--kite-name', help='Name of the kite (eg: mybox.pagekite.me)') set_kite.add_argument('--kite-secret', help='Secret for the kite') # Get service status get_service = subparsers.add_parser('get-service-status', help='Get whether service is enabled') get_service.add_argument('service', choices=['http', 'ssh']) # Set service status set_service = subparsers.add_parser('set-service-status', help='Enable/disable a service') set_service.add_argument('service', choices=['http', 'ssh']) set_service.add_argument('enable', choices=['enable', 'disable']) return parser.parse_args() def subcommand_start(_): """Start PageKite service""" status = subprocess.call(['service', 'pagekite', 'start']) if status: raise Exception('Unable to start PageKite server') def subcommand_stop(_): """Stop PageKite service""" status = subprocess.call(['service', 'pagekite', 'stop']) if status: raise Exception('Unable to stop PageKite server') def subcommand_get_status(_): """Print status of the pagekite service""" is_enabled = is_pagekite_enabled() print('enabled' if is_enabled else 'disabled') def is_pagekite_enabled(): conf = filecache[PATHS['account']] return 'abort_not_configured' not in conf def subcommand_set_status(arguments): """Enable/disable the pagekite service""" enable = arguments.enable == 'enable' is_enabled = is_pagekite_enabled() if enable and is_enabled: print('already enabled') return if not enable and not is_enabled: print('already disabled') return if enable: pagekite_enable() print('enabled') else: pagekite_disable() print('disabled') def subcommand_get_frontend(_): """Get pagekite frontend url""" import ipdb; ipdb.set_trace() conf = filecache[PATHS['frontends']] url = conf['frontend'] #url = aug.get(PATHS['frontend']) print(url if url else "") def subcommand_set_frontend(arguments): """Set pagekite frontend url and disable default pagekite.net frontend""" aug.remove(PATHS['defaults']) aug.set(PATHS['frontend'], arguments.url) aug.save() def subcommand_get_pagekitenet_frontend_status(_): match = aug.match(PATHS['defaults']) print("enabled" if match else "disabled") def subcommand_enable_pagekitenet_frontend(_): """Enable using default pageket.net frontend This disables any other frontends. """ aug.set(PATHS['defaults'], '') aug.remove(PATHS['frontend']) aug.save() print("enabled") def subcommand_disable_pagekitenet_frontend(_): aug.remove(PATHS['defaults']) aug.save() print("disabled") def pagekite_enable(): """Enable the pagekite daemon""" aug.remove(PATHS['abort_not_configured']) aug.save() def pagekite_disable(): """Disable the pagekite daemon""" aug.set(PATHS['abort_not_configured'], '') aug.save() def subcommand_get_service_status(arguments): """Print status of the pagekite service""" is_enabled = bool(get_enabled_service_paths(arguments.service)) print('enabled' if is_enabled else 'disabled') def subcommand_get_kite(_): """Print details of the currently configured kite""" kitename = aug.get(PATHS['kitename']) kitesecret = aug.get(PATHS['kitesecret']) print(kitename if kitename else '') print(kitesecret if kitesecret else '') def subcommand_set_kite(arguments): """Set details of the kite""" aug.set(PATHS['kitename'], arguments.kite_name) aug.set(PATHS['kitesecret'], arguments.kite_secret) aug.save() def subcommand_set_service_status(arguments): """Enable/disable a pagekite service""" enable = arguments.enable == 'enable' is_enabled = bool(get_enabled_service_paths(arguments.service)) if enable and is_enabled: print('already enabled') return if not enable and not is_enabled: print('already disabled') return if enable: service_enable(arguments.service) print('enabled') else: service_disable(arguments.service) print('disabled') def service_enable(service): """Enable a service""" position = len(aug.match(PATHS[service])) + 1 root = os.path.join(PATHS[service], str(position)) set_service(root, *SERVICES[service]) def set_service(root, source, destination, secret=None): """Set service_on with the given augeas root (path)""" aug.set(os.path.join(root, 'source'), source) aug.set(os.path.join(root, 'destination'), destination) if secret is not None: aug.set(os.path.join(root, 'secret'), secret) aug.save() def get_enabled_service_paths(service): """Search all service_on lines of the given protocol""" paths = [] for i, match in enumerate(aug.match(PATHS[service]), start=1): service_path = os.path.join(match, str(i)) source = aug.get(os.path.join(service_path, 'source')) if service == "ssh" and "raw/22" in source: paths.append(service_path) elif service == "http" and service in source: paths.append(service_path) return paths def service_disable(service): """Disable a service""" # TODO: saving config files after changing/deleting config entries with # augeas fails with an IOError. # Saving the file after removing this path does not work: # /files/etc/pagekite.d/80_httpd.rc/service_on/1 # Saving the file after removing this path works: # /files/etc/pagekite.d/80_httpd.rc/service_on # # This could be an augeas bug, see # http://permalink.gmane.org/gmane.comp.sysutils.augeas.devel/5419 # Once this problem is solved, all you should need in this function is: # # paths = get_enabled_service_paths(service) # [aug.remove(path) for path in paths] # aug.save() if service == "ssh": path = '/etc/pagekite.d/80_sshd.rc' activated_pattern = 'raw/22' elif service == "http": path = '/etc/pagekite.d/80_httpd.rc' activated_pattern = 'http' with open(path, 'r') as file: lines = file.readlines() for i, line in enumerate(lines): if line.startswith('service_on') and activated_pattern in line: lines[i] = "#%s" % line break with open(path, 'w') as file: file.writelines(lines) def main(): """Parse arguments and perform all duties""" arguments = parse_arguments() subcommand = arguments.subcommand.replace('-', '_') subcommand_method = globals()['subcommand_' + subcommand] subcommand_method(arguments) if __name__ == "__main__": main()