class AuthorizedKeys(list): """ Parser for OpenSSH authorized_keys file contents """ def __init__(self, path=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.path = path self.load() def load(self): """ Load authorized keys file, discarding earlier data in the object. SSH v1 authorized_keys line: options, bits, exponent, modulus, comment) SSH v2 authorized_keys line: options, keytype, base64-encoded key, comment)' See man sshd for details. """ self.__delslice__(0, len(self)) if not os.path.isfile(self.path): self.log.debug('No such file: {0}'.format(self.path)) return for line in [l.rstrip() for l in open(self.path, 'r').readlines()]: if line.startswith('#') or line.strip() == '': continue try: self.append(OpenSSHPublicKey(line)) except SSHKeyError: pass
def __init__(self, name=None, description=None, epilog=None, debug_flag=True): self.name = os.path.basename(sys.argv[0]) setproctitle('%s %s' % (self.name, ' '.join(sys.argv[1:]))) signal.signal(signal.SIGINT, self.SIGINT) reload(sys) sys.setdefaultencoding('utf-8') if name is None: name = self.name # Set to True to avoid any messages from self.message to be printed self.silent = False self.logger = Logger(self.name) self.log = self.logger.default_stream self.subcommand_parser = None self.parser = argparse.ArgumentParser( prog=name, description=description, formatter_class=argparse.RawTextHelpFormatter, epilog=epilog, add_help=True, conflict_handler='resolve', ) if debug_flag: self.parser.add_argument('--debug', action='store_true', help='Show debug messages')
class KnownHosts(list): """ Parser for OpenSSH known_hosts file contents """ def __init__(self, path=DEFAULT_KNOWN_HOSTS, fingerprint_hash=None): self.log = Logger().default_stream self.path = path self.fingerprint_hash = fingerprint_hash self.load() def load(self): """ Load known hosts file, discarding earlier data in the object. """ del self[0:len(self)] if not os.path.isfile(self.path): self.log.debug('No such file: {}'.format(self.path)) return for line in [l.rstrip() for l in open(self.path, 'r').readlines()]: if line.startswith('#') or line.strip() == '': continue # Strip list of hosts from line hosts, key = line.split(None, 1) hosts = hosts.split(',') try: key = KnownHostsEntry(key) if key not in self: self.append(key) else: key = self[self.index(key)] key.add_hosts(hosts) except SSHKeyError: pass def save(self, path=None): """Save known hosts Saves known hosts file, merging same keys to single line """ if path is None: path = self.path try: with open(path, 'w') as fd: for entry in self: fd.write('{}\n'.format(entry)) except Exception as e: raise SSHKeyError('Error writing {}: {}'.format(path, e)) def find_host_key(self, value): """Find key for hostname """ for key in self: if value in key.hosts: return key return None
class UserSSHKeys(dict): """ List of user configured SSH keys to process """ def __init__(self, authorized_keys=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.__parse_user_keyfiles() self.authorized_keys = AuthorizedKeys(authorized_keys) def __parse_user_keyfiles(self): """ Parses any normal user keyfiles in ~/.ssh directory automatically. These keys are marked by default not to be automatically loaded: to enable, add key path to ~/.ssh/sshkeys.conf """ user_sshdir = os.path.expanduser('~/.ssh') if not os.path.isdir(user_sshdir): return paths = filter(lambda x: os.path.isfile(x), [ os.path.join(user_sshdir, x) for x in filter( lambda x: x not in SSH_CONFIG_FILES and os.path.splitext(x)[1] != '.pub', os.listdir(user_sshdir)) ]) for path in paths: try: sshkey = SSHKeyFile(self, path) except SSHKeyError, emsg: self.log.debug(emsg) continue self[sshkey.path] = sshkey
def __init__(self, path=None): self.defaults = {} self.patterns = [] self.log = Logger().default_stream self.path = path is not None and path or os.path.expanduser( '~/.ssh/config') self.reload()
def __init__(self, config, name, **kwargs): self.log = Logger().default_stream self.name = name self.description = name self.connect_command = DEFAULT_CONNECT_COMMAND self.command_separator = DEFAULT_COMMAND_SEPARATOR self.update_commands = [] self.servers = [] for k in ('description', 'command_separator'): if k in kwargs: setattr(self, k, kwargs[k]) if 'connect' in kwargs: self.connect_command = kwargs['connect'] if 'commands' in kwargs: self.update_commands = kwargs['commands'] if 'servers' in kwargs: names = kwargs['servers'] if not isinstance(names, list): names = [names] for name in names: self.servers.append(Server(self, name)) self.modified = False
def __init__(self, oid): self.log = Logger('snmp').default_stream self.oid = self.__format_oid__(oid) self.oid_string = self.__format_oid_string__(oid) self._parent = None self._next = None
def __init__(self, path): self.operating_systems = [] self.servers = [] self.path = path self.log = Logger().default_stream if os.path.isfile(self.path): self.load()
def __init__(self, oid, reload_interval=60): Script.__init__(self) self.log = Logger('snmp').default_stream self.reload_interval = reload_interval self.last_reload = None self.add_argument('-g', '--get', help='SNMP GET request') self.add_argument('-n', '--next', help='SNMP GET request') self.add_argument('-t', '--tree', action='store_true', help='Show OID tree') self.tree = Tree(oid)
def __init__(self, user_keys, path): self.log = Logger().default_stream self.user_keys = user_keys self.path = os.path.realpath(path) self.available = os.access(path, os.R_OK) and True or False self.autoload = False self.update({ 'bits': None, 'fingerprint': None, 'path': None, 'algorithm': None, }) public_key = '%s.pub' % self.path if not os.path.isfile(public_key): self.available = False return cmd = ( 'ssh-keygen', '-l', '-f', public_key ) p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) (stdout, stderr) = p.communicate() l = stdout.split('\n')[0].rstrip() if p.returncode != 0: raise SSHKeyError('ERROR parsing public key: %s' % public_key) m = RE_KEYINFO.match(l) if not m: raise SSHKeyError('Unsupported public key output: %s' % l) for k, v in m.groupdict().items(): if k == 'path': k = 'public_key_path' self[k] = v
def __init__(self, name=None, description=None, epilog=None, debug_flag=True): self.name = os.path.basename(sys.argv[0]) setproctitle('%s %s' % (self.name, ' '.join(sys.argv[1:]))) signal.signal(signal.SIGINT, self.SIGINT) reload(sys) sys.setdefaultencoding('utf-8') if name is None: name = self.name # Set to True to avoid any messages from self.message to be printed self.silent = False self.logger = Logger(self.name) self.log = self.logger.default_stream self.subcommand_parser = None self.parser = argparse.ArgumentParser( prog=name, description=description, formatter_class=argparse.RawTextHelpFormatter, epilog=epilog, add_help=True, conflict_handler='resolve', ) if debug_flag: self.parser.add_argument('--debug', action='store_true', help='Show debug messages') self.parser.add_argument('--insecure', action='store_false', help='No HTTPS certificate validation') self.parser.add_argument('-B', '--browser', choices=('chrome','chromium','firefox'), help='Browser for cookie stealing' )
def __init__(self, config, name, **kwargs): self.log = Logger().default_stream self.name = name self.description = name self.connect_command = DEFAULT_CONNECT_COMMAND self.command_separator = DEFAULT_COMMAND_SEPARATOR self.update_commands = [] self.servers = [] for k in ( 'description', 'command_separator', ): if k in kwargs: setattr(self, k, kwargs[k]) if 'connect' in kwargs: self.connect_command = kwargs['connect'] if 'commands' in kwargs: self.update_commands = kwargs['commands'] if 'servers' in kwargs: names = kwargs['servers'] if not isinstance(names, list): names = [names] for name in names: self.servers.append(Server(self, name)) self.modified = False
def __init__(self, db_path, tables_sql=None, foreign_keys=True): """ Opens given database reference. If tables_sql list is given, each SQL command in the list is executed to initialize the database. """ self.log = Logger('sqlite').default_stream self.db_path = db_path if db_path is None: raise SQLiteError('Database path is None') db_dir = os.path.dirname(db_path) if db_dir and not os.path.isdir(db_dir): try: os.makedirs(db_dir) except IOError as e: raise SQLiteError('Error creating directory {0}: {1}'.format( db_dir, e)) self.conn = sqlite3.Connection(self.db_path) c = self.cursor if foreign_keys: c.execute('PRAGMA foreign_keys=ON') c.fetchone() if tables_sql: for q in tables_sql: try: c.execute(q) except sqlite3.OperationalError as e: raise SQLiteError('Error executing SQL:\n{0}\n{1}'.format( q, e))
def __init__(self, value=None, input_format=None): self.log = Logger('dates').default_stream if value is None: self.value = datetime.now().date() elif isinstance(value, Day): self.value = value.value elif isinstance(value, date): self.value = value elif isinstance(value, datetime): self.value = value.date() elif isinstance(value, time.struct_time): self.value = date(*value[:3]) elif isinstance(value, str) and input_format is None and value == '': self.value = datetime.now().date() else: input_format = input_format is not None and input_format or DEFAULT_DATE_FORMAT try: self.value = datetime.strptime(str(value), input_format).date() except ValueError: try: self.value = date(*time.localtime(int(value))[:3]) except ValueError: raise DatesError('Error parsing date: {0}'.format(value))
def __init__(self, value=None, input_format=DEFAULT_DATE_FORMAT, firstweekday=WEEK_START_DEFAULT, workdays=None, workdays_per_week=WORKDAYS_PER_WEEK): self.__next = 0 self.log = Logger('dates').default_stream day = Day(value=value, input_format=input_format) if firstweekday in WEEKDAY_NAMES: self.firstweekday = WEEKDAY_NAMES.index(firstweekday) else: try: self.firstweekday = int(firstweekday) if self.firstweekday < 0 or self.firstweekday > 6: raise ValueError except ValueError: raise ValueError( 'Invalid first week day index: {0}'.format(firstweekday)) wday = (day.value.isoweekday() + (7 - self.firstweekday)) % 7 self.first = day - wday self.last = self.first + 6 self.weeknumber = int(self.first.value.strftime('%U')) self.workdays = [] if workdays is not None: if not isinstance(workdays, iterable): raise ValueError( 'Invalid workdays index list parameter: {0}'.format( workdays)) for i in workdays: try: i = int(i) if i < 0 or i > 6: raise ValueError self.workdays.append(self.first + i) except ValueError: raise ValueError( 'Invalid workdays index list parameter: {0}'.format( workdays)) else: try: workdays_per_week = int(workdays_per_week) if workdays_per_week < 0 or workdays_per_week > 7: raise ValueError except ValueError: raise ValueError( 'Invalid value for workdays_per_week: {0}'.format( workdays_per_week)) self.workdays = [ self[i] for i in filter(lambda i: i <= 6, range(0, workdays_per_week)) ] self.workdays.sort()
def __init__(self, name=None, description=None, epilog=None, debug_flag=True): self.name = os.path.basename(sys.argv[0]) signal.signal(signal.SIGINT, self.SIGINT) if has_setproctitle: setproctitle('{0} {1}'.format(self.name, ' '.join(sys.argv[1:]))) if sys.version_info.major < 3: reload(sys) sys.setdefaultencoding('utf-8') if name is None: name = self.name # Set to True to avoid any messages from self.message to be output self.silent = False self.logger = Logger(self.name) self.log = self.logger.default_stream self.parser = argparse.ArgumentParser( prog=name, description=description, formatter_class=argparse.RawTextHelpFormatter, epilog=epilog, add_help=True, conflict_handler='resolve', ) if debug_flag: self.parser.add_argument('--debug', action='store_true', help='Show debug messages') self.subcommand_parser = None
def __init__(self, name): super(threading.Thread, self).__init__() self.log = Logger(name).default_stream self.status = 'not running' self.setDaemon(True) self.setName(name) self._stop_event = threading.Event()
def __init__(self, src, dst, flags=DEFAULT_RSYNC_FLAGS, output_format=DEFAULT_OUTPUT_FORMAT): self.log = Logger('rsync').default_stream self.src = src self.dst = dst self.flags = flags self.output_format = output_format cmd = CommandPathCache().which('rsync') if cmd is None: raise RsyncError('No such command: rsync') self.command = [cmd] + flags + [ self.output_format, '{0}'.format(src), '{0}'.format(dst) ]
def __init__(self, runner, show_colors=False): self.log = Logger().default_stream self.runner = runner self.show_colors = show_colors self.results = { 'contacted': self.resultset_loader(self, 'contacted'), 'dark': self.resultset_loader(self, 'dark'), }
def __init__(self,flags=[]): self.log = Logger('filesystems').default_stream if isinstance(flags, list): for k in flags: self.set(k) if isinstance(flags, dict): for k, v in flags.items(): self.set(k, v)
def __init__(self,src,dst,flags=DEFAULT_RSYNC_FLAGS,output_format=DEFAULT_OUTPUT_FORMAT): self.log = Logger('rsync').default_stream self.src = src self.dst = dst self.flags = flags self.output_format = output_format cmd = CommandPathCache().which('rsync') if cmd is None: raise RsyncError('No such command: rsync') self.command = [cmd] + flags + [ self.output_format, '{0}'.format(src),'{0}'.format(dst) ]
class SSHConfig(dict): def __init__(self, path=None): self.defaults = {} self.patterns = [] self.log = Logger().default_stream self.path = path is not None and path or os.path.expanduser( '~/.ssh/config') self.reload() def reload(self): self.clear() if not os.path.isfile(self.path): self.log.debug('No such file: {}'.format(self.path)) return with open(self.path, 'r') as fd: host = None for line in [x.strip() for x in fd.readlines()]: if line == '' or line.startswith('#'): continue if line[:5] == 'Host ': host = SSHConfigHostPattern(self, line[5:]) for pattern in host.patterns: self[pattern] = host self.patterns.append(host) else: host.parse(line) if '*' in self.keys(): self.defaults.update(self.pop('*').items()) def keys(self): return [k for k in sorted(super(SSHConfig, self).keys())] def items(self): return [(k, self[k]) for k in self.keys()] def values(self): return [self[k] for k in self.keys()]
class SSHConfig(dict): def __init__(self, path=None): self.defaults = {} self.patterns = [] self.log = Logger().default_stream self.path = path is not None and path or os.path.expanduser('~/.ssh/config') self.reload() def reload(self): self.clear() if not os.path.isfile(self.path): self.log.debug('No such file: {}'.format(self.path)) return with open(self.path, 'r') as fd: host = None for line in [x.strip() for x in fd.readlines()]: if line == '' or line.startswith('#'): continue if line[:5] == 'Host ': host = SSHConfigHostPattern(self, line[5:]) for pattern in host.patterns: self[pattern] = host self.patterns.append(host) else: host.parse(line) if '*' in self.keys(): self.defaults.update(self.pop('*').items()) def keys(self): return [k for k in sorted(super(SSHConfig, self).keys())] def items(self): return [(k, self[k]) for k in self.keys()] def values(self): return [self[k] for k in self.keys()]
def __init__(self, mountpoints, device, mountpoint, filesystem, flags=None): self.log = Logger('filesystems').default_stream self.mountpoints = mountpoints self.device = device self.mountpoint = mountpoint self.filesystem = filesystem self.flags = FileSystemFlags(flags=flags if flags is not None else {}) self.usage = {}
class SSHConfig(dict): def __init__(self, path=None): self.defaults = {} self.log = Logger().default_stream self.path = path is not None and path or os.path.expanduser('~/.ssh/config') self.reload() def reload(self): self.clear() if not os.path.isfile(self.path): self.log.debug('No such file: %s' % self.path) return with open(self.path, 'r') as fd: host = None for l in [x.strip() for x in fd.readlines()]: if l=='' or l.startswith('#'): continue if l[:5]=='Host ': host = SSHConfigHost(self, l[5:]) self[host.name] = host else: host.parse(l) if '*' in self.keys(): self.defaults.update(self.pop('*').items()) def keys(self): return [k for k in sorted(dict.keys(self))] def items(self): return [(k, self[k]) for k in self.keys()] def values(self): return [self[k] for k in self.keys()]
def __init__(self,value=None, input_format=DEFAULT_DATE_FORMAT, firstweekday=WEEK_START_DEFAULT): self.__next = 0 self.log = Logger('dates').default_stream self.first = Day(Day(value=value, input_format=input_format).value.replace(day=1)) self.days = calendar.monthrange(self.first.value.year,self.first.value.month)[1] self.last = self.first+(self.days-1) self.firstweekday = firstweekday self.weeks = [] week = Week(self.first, None, firstweekday=self.firstweekday) while week.first <= self.last: self.weeks.append(week) week+=1
class UserSSHKeys(dict): """ List of user configured SSH keys to process """ def __init__(self, authorized_keys=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.__parse_user_keyfiles() self.authorized_keys = AuthorizedKeys(authorized_keys) def __parse_user_keyfiles(self): """ Parses any normal user keyfiles in ~/.ssh directory automatically. These keys are marked by default not to be automatically loaded: to enable, add key path to ~/.ssh/sshkeys.conf """ user_sshdir = os.path.expanduser('~/.ssh') if not os.path.isdir(user_sshdir): return paths = filter(lambda x: os.path.isfile(x), [os.path.join(user_sshdir, x) for x in filter(lambda x: x not in SSH_CONFIG_FILES and os.path.splitext(x)[1]!='.pub', os.listdir(user_sshdir) )] ) for path in paths: try: sshkey = SSHKeyFile(self, path) except SSHKeyError, emsg: self.log.debug(emsg) continue self[sshkey.path] = sshkey
class RsyncCommand(object): """ Wrapper to execute rsync to target nicely from python """ def __init__(self, src, dst, flags=DEFAULT_RSYNC_FLAGS, output_format=DEFAULT_OUTPUT_FORMAT): self.log = Logger('rsync').default_stream self.src = src self.dst = dst self.flags = flags self.output_format = output_format cmd = CommandPathCache().which('rsync') if cmd is None: raise RsyncError('No such command: rsync') self.command = [cmd] + flags + [ self.output_format, '{0}'.format(src), '{0}'.format(dst) ] def __str__(self): return ' '.join(self.command) def run(self, verbose=False): """ Run the rsync command specific in __init__() """ if not verbose and self.log.level == logging.DEBUG: verbose = True try: p = Popen(self.command, stdin=PIPE, stdout=PIPE, stderr=PIPE) self.log.debug('Running: {0}'.format(self)) rval = None while rval is None: if verbose: while True: line = p.stdout.readline() if line == '': break sys.stdout.write('{0}\n'.format(line.rstrip())) time.sleep(0.2) rval = p.poll() self.log.debug('Return code: {0}'.format(rval)) if rval != 0: raise RsyncError('Error running command {0}: {1}'.format( self, p.stderr.read())) except KeyboardInterrupt: self.log.debug('Rsync interrupted') raise KeyboardInterrupt
def __init__(self, db_path, tables_sql=None, foreign_keys=True): """ Opens given database reference. If tables_sql list is given, each SQL command in the list is executed to initialize the database. """ self.log = Logger('sqlite').default_stream self.db_path = db_path if db_path is None: raise SQLiteError('Database path is None') db_dir = os.path.dirname(db_path) if not os.path.isdir(db_dir): try: os.makedirs(db_dir) except IOError, (ecode, emsg): raise SQLiteError('Error creating directory %s: %s' % (db_dir, emsg))
class RsyncCommand(object): """ Wrapper to execute rsync to target nicely from python """ def __init__(self, src, dst, flags=DEFAULT_RSYNC_FLAGS, output_format=DEFAULT_OUTPUT_FORMAT): self.log = Logger('rsync').default_stream self.src = src self.dst = dst self.flags = flags self.output_format = output_format cmd = CommandPathCache().which('rsync') if cmd is None: raise RsyncError('No such command: rsync') self.command = [cmd] + flags + [self.output_format, '{0}'.format(src), '{0}'.format(dst)] def __str__(self): return ' '.join(self.command) def run(self, verbose=False): """ Run the rsync command specific in __init__() """ if not verbose and self.log.level == logging.DEBUG: verbose = True try: p = Popen(self.command, stdin=PIPE, stdout=PIPE, stderr=PIPE) self.log.debug('Running: {0}'.format(self)) rval = None while rval is None: if verbose: while True: line = p.stdout.readline() if line == '': break sys.stdout.write('{0}\n'.format(line.rstrip())) time.sleep(0.2) rval = p.poll() self.log.debug('Return code: {0}'.format(rval)) if rval != 0: raise RsyncError('Error running command {0}: {1}'.format(self, p.stderr.read())) except KeyboardInterrupt: self.log.debug('Rsync interrupted') raise KeyboardInterrupt
def __init__(self, user_keys, path): self.log = Logger().default_stream self.user_keys = user_keys self.path = os.path.realpath(path) self.available = os.access(path, os.R_OK) and True or False self.autoload = False self.update({ 'bits': None, 'fingerprint': None, 'path': None, 'algorithm': None }) public_key = '{}.pub'.format(self.path) if not os.path.isfile(public_key): self.available = False return cmd = ('ssh-keygen', '-l', '-f', public_key) p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) (stdout, stderr) = [str(x, 'utf-8') for x in p.communicate()] line = stdout.split('\n')[0].rstrip() if p.returncode != 0: raise SSHKeyError( 'ERROR parsing public key: {}'.format(public_key)) data = parse_public_key_line_pattern(line) if not data: raise SSHKeyError('Unsupported public key output: {}'.format(line)) for k, v in data.items(): if k == 'path': k = 'public_key_path' self[k] = v
class Script(object): """ Class for common CLI tool script """ def __init__(self, name=None, description=None, epilog=None, debug_flag=True): self.name = os.path.basename(sys.argv[0]) signal.signal(signal.SIGINT, self.SIGINT) if has_setproctitle: setproctitle('{0} {1}'.format(self.name, ' '.join(sys.argv[1:]))) if sys.version_info.major < 3: reload(sys) # noqa sys.setdefaultencoding('utf-8') if name is None: name = self.name # Set to True to avoid any messages from self.message to be output self.silent = False self.logger = Logger(self.name) self.log = self.logger.default_stream self.parser = argparse.ArgumentParser( prog=name, description=description, formatter_class=argparse.RawTextHelpFormatter, epilog=epilog, add_help=True, conflict_handler='resolve', ) if debug_flag: self.parser.add_argument('--debug', action='store_true', help='Show debug messages') self.subcommand_parser = None def SIGINT(self, signum, frame): """ Parse SIGINT signal by quitting the program cleanly with exit code 1 """ for t in [t for t in threading.enumerate() if t.name != 'MainThread']: if hasattr(t, 'stop') and callable(t.stop): t.stop() for t in [t for t in threading.enumerate() if t.name != 'MainThread']: t.join() self.exit(1) def wait(self, poll_interval=1): """ Wait for running threads to finish. Poll interval is time to wait between checks for threads """ while True: active = [t for t in threading.enumerate() if t.name != 'MainThread'] if not len(active): break self.log.debug('Waiting for {0:d} threads'.format(len(active))) time.sleep(poll_interval) def exit(self, value=0, message=None): """ Exit the script with given exit value. If message is not None, it is output to stdout """ if isinstance(value, bool): if value: value = 0 else: value = 1 else: try: value = int(value) if value < 0 or value > 255: raise ValueError except ValueError: value = 1 if message is not None: self.message(message) for t in [t for t in threading.enumerate() if t.name != 'MainThread']: if hasattr(t, 'stop') and callable(t.stop): t.stop() while True: active = [t for t in threading.enumerate() if t.name != 'MainThread'] if not len(active): break time.sleep(0.1) sys.exit(value) def message(self, message): if self.silent: return sys.stdout.write('{0}\n'.format(message)) def error(self, message): sys.stderr.write('{0}\n'.format(message)) def add_subcommand(self, command): """Add a subcommand parser instance Register named subcommand parser to argument parser Subcommand parser must be an instance of ScriptCommand class. Example usage: class ListCommand(ScriptCommand): def run(self, args): self.message('Listing stuff') parser.add_subcommand(ListCommand('list', 'List stuff from script')) """ if self.subcommand_parser is None: self.subcommand_parser = self.parser.add_subparsers( dest='command', help='Please select one command mode below', title='Command modes' ) self.subcommands = {} if not isinstance(command, ScriptCommand): raise ScriptError('Subcommand must be a ScriptCommand instance') parser = self.subcommand_parser.add_parser( command.name, help=command.short_description, description=command.description, epilog=command.epilog, formatter_class=argparse.RawTextHelpFormatter, ) self.subcommands[command.name] = command command.script = self if callable(getattr(command, '__register_arguments__', None)): command.__register_arguments__(parser) return parser def usage_error(self, *args, **kwargs): return self.parser.error(*args, **kwargs) def add_argument(self, *args, **kwargs): """ Shortcut to add argument to main argumentparser instance """ self.parser.add_argument(*args, **kwargs) def __process_args__(self, args): """Process args Process args from parse_*args CalledProcessError """ if getattr(args, 'debug', None): self.logger.set_level('DEBUG') elif getattr(args, 'quiet', None): self.silent = True elif getattr(args, 'verbose', None): self.logger.set_level('INFO') if self.subcommand_parser is not None and args.command is not None: if hasattr(self.subcommands[args.command], 'parse_args'): args = self.subcommands[args.command].parse_args(args) self.subcommands[args.command].run(args) return args def parse_args(self): """ Call parse_args for parser and check for default logging flags """ return self.__process_args__(self.parser.parse_args()) def parse_known_args(self): """ Call parse_args for parser and check for default logging flags """ args, other_args = self.parser.parse_known_args() args = self.__process_args__(args) return args, other_args def execute(self, args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, dryrun=False): """ Default wrapper to execute given interactive shell command with standard stdin, stdout and stderr """ if isinstance(args, str): args = args.split() if not isinstance(args, list): raise ValueError('Execute arguments must be a list') if dryrun: self.log.debug('would execute: {0}'.format(' '.join(args))) return 0 p = Popen(args, stdin=stdin, stdout=stdout, stderr=stderr) p.wait() return p.returncode def check_output(self, args): """ Wrapper for subprocess.check_output to be executed in script context """ if isinstance(args, str): args = [args] try: return check_output(args) except IOError as e: raise ScriptError(e) except OSError as e: raise ScriptError(e) except CalledProcessError as e: raise ScriptError(e)
class Server(object): """ Configuration for one server """ def __init__(self, osgroup, name, description=None): self.log = Logger().default_stream self.osgroup = osgroup self.name = name self.description = description def __repr__(self): if self.description is not None: return '{0} ({1})'.format(self.name, self.description) else: return '{0}'.format(self.name) def __eq__(self, other): if isinstance(other, str): return self.name == other return self.name == other.name def __ne__(self, other): if isinstance(other, str): return self.name != other return self.name != other.name def __gt__(self, other): if isinstance(other, str): return self.name > other return self.name > other.name def __lt__(self, other): if isinstance(other, str): return self.name < other return self.name < other.name def __ge__(self, other): if isinstance(other, str): return self.name >= other return self.name >= other.name def __le__(self, other): if isinstance(other, str): return self.name <= other return self.name <= other.name @property def connect_command(self): return [ x == 'SERVER' and self.name or x for x in self.osgroup.connect_command ] def check_output(self, command): """ Wrapper to execute and check output of a command without interactive console actions Splits string automatically to words so pass a list if you need to escape stuff properly. """ if not isinstance(command, list): command = [command] cmd = self.connect_command + command p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) (stdout, stderr) = p.communicate() return p.returncode, stdout.rstrip('\n'), stderr.rstrip('\n') def shell(self, command): """ Wrapper to run interactive shell command on the host, using current terminal for stdin, stdout and stderr Splits string automatically to words so pass a list if you need to escape stuff properly. Returns command return code """ if not isinstance(command, list): command = [command] cmd = self.connect_command + command p = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) p.wait() return p.returncode def update(self): """ Wrapper to call correct update commands for this host """ if not self.osgroup.update_commands: self.log.debug('No update commands for OS {0}'.format( self.osgroup.name)) return self.log.debug("Running: {0} '{1}'".format( ' '.join(self.connect_command), self.osgroup.command_separator.join(self.osgroup.update_commands))) cmd = self.connect_command + [ self.osgroup.command_separator.join(self.osgroup.update_commands) ] p = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) p.wait() return p.returncode def remove(self): """Remove from configuration Remove this server from configuration """ return self.osgroup.remove_server(self)
class ServerConfigFile(object): """ Parser for configuration file describing servers and their operating systems. """ def __init__(self, path): self.operating_systems = [] self.servers = [] self.path = path self.log = Logger().default_stream if os.path.isfile(self.path): self.load() @property def osnames(self): return [x.name for x in self.operating_systems] def load(self): self.operating_systems = [] self.servers = [] try: config = ConfigObj(self.path) except ValueError as e: raise ValueError('Error parsing {0}: {1}'.format(self.path, e)) osgroup = None for key, section in config.items(): if 'commands' in section: if key in self.servers: raise ValueError( 'Duplicate OS group name: {0}'.format(key)) osgroup = OperatingSystemGroup(self, key, **section) self.operating_systems.append(osgroup) self.servers.extend(osgroup.servers) else: if osgroup is None: raise ValueError( 'Server in configuration file but no OS group defined yet' ) self.servers.append(Server(osgroup, section)) return def save(self): config = ConfigObj() for osgroup in self.operating_systems: section = { 'commands': osgroup.update_commands, } if osgroup.description is not None and osgroup.description != '': section['description'] = osgroup.description if osgroup.servers: section['servers'] = osgroup.servers if osgroup.connect_command != DEFAULT_CONNECT_COMMAND: section['connect'] = osgroup.connect_command if osgroup.command_separator != DEFAULT_COMMAND_SEPARATOR: section['command_separator'] = osgroup.command_separator config[osgroup.name] = section for server in osgroup.servers: if server.description: config[server.name] = {'description': server.description} self.log.debug('Saving configuration to {0}'.format(self.path)) config.write(outfile=open(self.path, 'w')) def match_os(self, name): for operating_system in self.operating_systems: if operating_system.name == name: return operating_system raise ValueError('Unknown OS: {0}'.format(name))
class OperatingSystemGroup(object): """ Group of operating systems in configuration file """ def __init__(self, config, name, **kwargs): self.log = Logger().default_stream self.name = name self.description = name self.connect_command = DEFAULT_CONNECT_COMMAND self.command_separator = DEFAULT_COMMAND_SEPARATOR self.update_commands = [] self.servers = [] for k in ('description', 'command_separator'): if k in kwargs: setattr(self, k, kwargs[k]) if 'connect' in kwargs: self.connect_command = kwargs['connect'] if 'commands' in kwargs: self.update_commands = kwargs['commands'] if 'servers' in kwargs: names = kwargs['servers'] if not isinstance(names, list): names = [names] for name in names: self.servers.append(Server(self, name)) self.modified = False def __repr__(self): return '{0}: {1} ({2:d} servers)'.format(self.name, self.description, len(self.servers)) @property def command_separator(self): return self._command_separator @command_separator.setter def command_separator(self, value): self._command_separator = value self.modified = True @property def connect_command(self): return self._connect_command @connect_command.setter def connect_command(self, value): if isinstance(value, str): value = value.split() self._connect_command = value self.modified = True @property def update_command(self): return self._update_commands @update_command.setter def update_commands(self, value): if isinstance(value, str): value = [value] self._update_commands = value self.modified = True @property def descripion(self): return self._descripion @descripion.setter def descripion(self, value): self._descripion = value self.modified = True def add_server(self, name): if name in [s.name for s in self.servers]: self.log.debug( 'Error adding: server already in group: {0}'.format(name)) return self.servers.append(Server(self, name)) self.modified = True def remove_server(self, name): try: server = [s for s in self.servers if s.name == name][0] self.servers.remove(server) self.modified = True except IndexError: self.log.debug( 'Error removing: server not in group: {0}'.format(name)) return
class AuthorizedKeys(dict): """ Parser for OpenSSH authorized_keys file contents """ def __init__(self, path=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.path = path self.load() def load(self): """ Load authorized keys file, discarding earlier data in the object. SSH v1 authorized_keys line: options, bits, exponent, modulus, comment) SSH v2 authorized_keys line: options, keytype, base64-encoded key, comment)' See man sshd for details. """ self.clear() if not os.path.isfile(self.path): self.log.debug('No such file: {0}'.format(self.path)) return for l in [l.rstrip() for l in open(self.path, 'r').readlines()]: if l.startswith('#') or l.strip() == '': continue entry = {} parts = l.split() if parts[0][0] in string.digits and len(parts) == 4: entry['keyformat'] = 1 entry['bits'] = int(parts[0]) entry['exponent'] = parts[1] entry['modulus'] = parts[2] entry['comment'] = parts[3] entry['options'] = None elif parts[0] in ( 'ssh-dsa', 'ssh-rsa', ): entry['keyformat'] = 2 entry['keytype'] = parts[0] entry['key_base64'] = parts[1] entry['comment'] = len(parts) > 2 and parts[2] or '' entry['options'] = None else: entry['options'] = parts[0] parts = parts[1:] if not parts: continue if parts[0] in ( 'ssh-dsa', 'ssh-rsa', ) and len(parts) == 3: entry['keyformat'] = 2 entry['keytype'] = parts[0] entry['key_base64'] = parts[1] entry['comment'] = parts[2] elif parts[0] in string.digits: entry['keyformat'] = 1 entry['keyformat'] = 1 entry['bits'] = int(parts[0]) entry['exponent'] = parts[1] entry['modulus'] = parts[2] entry['comment'] = parts[3]
def __init__(self, path=DEFAULT_AUTHORIZED_KEYS, fingerprint_hash=None): self.log = Logger().default_stream self.path = path self.fingerprint_hash = fingerprint_hash self.load()
class SNMPAgent(Script): """SNMP Agent implementation SNMP agent daemon for net-snmpd pass_persist mode Example usage: agent = SNMPAgent('.1.2.3.4') tree = agent.register_tree('1.2.3.4.1') tree.register('1.2.3.4.1.1', 'integer', 1) tree.register('1.2.3.4.1.2', 'integer', 2) agent.register_tree('1.2.3.4.2') agent.register('1.2.3.4.2.1', 'integer', 1) agent.register('1.2.3.4.2.4', 'integer', 4) agent.register('1.2.3.4.2.3', 'integer', 3) agent.register('1.2.3.4.2.2', 'integer', 2) tree = agent.register_tree('1.2.3.4.3') tree.register('1.2.3.4.3.1.1', 'integer', 1) tree.add_values('string', [x for x in string.digits[2:]]) tree = agent.register_tree('1.2.3.4.4') tree.add_prefix_map([ { 'oid': '1.2.3.4.4.1', 'key': 'string', 'values': [x for x in string.letters[:10]]}, { 'oid': '1.2.3.4.4.2', 'key': 'integer', 'values': [x for x in string.digits[:10]]}, ]) agent.run() """ def __init__(self, oid, reload_interval=60): super(SNMPAgent, self).__init__() self.log = Logger('snmp').default_stream self.reload_interval = reload_interval self.last_reload = None self.args = None self.add_argument('-g', '--get', help='SNMP GET request') self.add_argument('-n', '--next', help='SNMP GET request') self.add_argument('-t', '--tree', action='store_true', help='Show OID tree') self.tree = Tree(oid) def __SIGHUP__(self, signum, frame): """ Signal handler to reload configuration. Note this requires also the IOError processing in main input loop below """ self.log.debug('Reloading from signal') self.reload() def parse_args(self): if self.args is None: self.args = super(SNMPAgent, self).parse_args() return self.args def register_tree(self, oid): return self.tree.add(Tree(oid)) def register(self, oid, key, value): return self.tree.add(Item(oid, key, value)) def GET(self, oid): """GET from agent Return given OID from agent tree or None """ try: return self.tree.GET(oid) except SNMPError as e: self.log.debug(e) return None def NEXT(self, oid): """NEXT from agent Return given NEXT from agent tree or None """ try: return self.tree.NEXT(oid) except SNMPError as e: self.log.debug(e) return None def SET(self, oid, value): """SET registered OID Set value for registered OID in agent tree. Note: By default items registered are readonly. """ return self.tree.SET(oid, value) def clear(self): return self.tree.clear() def reload(self): """ This method must be implemented in a child class. It is used to reload the SNMP tree data from files, if possible. """ raise NotImplementedError('You must implement reload in child class') def run(self): """SNMP Agent main loop Main loop to execute for agent. You can either run this in: - 'pass' mode: get/next for single OID by passing in OptionParser 'options' value 'get' or 'next' (snmpd.conf 'pass' agent) - 'pass_persist' mode: without any options, in which case the loop acts as permanent snmpd pass_persist agent. """ self.args = self.parse_args() signal.signal(signal.SIGHUP, self.__SIGHUP__) if self.args.tree: self.message(self.tree.oid_string) elif self.args.get: entry = self.GET(self.args.get) if entry is not None: sys.stdout.write('{0}\n'.format(entry.get_response)) elif self.args.next: entry = self.NEXT(self.args.next) if entry is not None: sys.stdout.write('{0}\n'.format(entry.next_response)) if self.args.tree or self.args.get or self.args.next: self.exit(0) EOF = '' self.log.debug('Starting SNMP agent for OID {0}'.format(self.tree.oid)) while True: ready = select.select([sys.stdin], [], [], 0.2)[0] if not ready: if self.last_reload is not None: since_reload = time.time() - self.last_reload if since_reload > self.reload_interval: self.reload() self.last_reload = time.time() else: self.last_reload = time.time() continue try: cmd = sys.stdin.readline() if cmd == EOF: break if cmd.strip() == '': self.log.debug('Received agent shutdown') break cmd = cmd.rstrip().lower() if cmd == 'quit': return if cmd == 'reload': self.reload() if cmd == 'ping': sys.stdout.write('PONG\n') if cmd in ('set', 'get', 'getnext'): oid = sys.stdin.readline() if oid == EOF: break oid = oid.rstrip() if cmd == 'set': value = sys.stdin.readline() if value == EOF: break result = self.SET(oid, value) sys.stdout.write('{0}\n'.format(result)) elif cmd == 'get': entry = self.GET(oid) if entry is not None: sys.stdout.write('{0}\n'.format(entry.get_response)) else: sys.stdout.write('NONE\n') elif cmd == 'getnext': entry = self.NEXT(oid) if entry is not None: sys.stdout.write('{0}\n'.format(entry.next_response)) else: sys.stdout.write('NONE\n') else: self.log.debug('Unknown command (should be get/set/getnext)') sys.stdout.flush() except IOError as e: # we get EINTR with SIGHUP and we can ignore it if e[0]==errno.EINTR: continue self.log.debug('IOError: {0}'.format(e)) return except KeyboardInterrupt: # Interactive mode, user interrupted self.log.debug('Quitting...') return
class ServerConfigFile(object): """ Parser for configuration file describing servers and their operating systems. """ def __init__(self, path): self.operating_systems = [] self.servers = [] self.path = path self.log = Logger().default_stream if os.path.isfile(self.path): self.load() @property def osnames(self): return [x.name for x in self.operating_systems] def load(self): self.operating_systems = [] self.servers = [] try: config = ConfigObj(self.path) except ValueError as e: raise ValueError("Error parsing {0}: {1}".format(self.path, e)) osgroup = None for key, section in config.items(): if "commands" in section: if key in self.servers: raise ValueError("Duplicate OS group name: {0}".format(key)) osgroup = OperatingSystemGroup(self, key, **section) self.operating_systems.append(osgroup) self.servers.extend(osgroup.servers) else: if osgroup is None: raise ValueError("Server in configuration file but no OS group defined yet") self.servers.append(Server(osgroup, section)) return def save(self): config = ConfigObj() for osgroup in self.operating_systems: section = {"commands": osgroup.update_commands} if osgroup.description is not None and osgroup.description != "": section["description"] = osgroup.description if osgroup.servers: section["servers"] = osgroup.servers if osgroup.connect_command != DEFAULT_CONNECT_COMMAND: section["connect"] = osgroup.connect_command if osgroup.command_separator != DEFAULT_COMMAND_SEPARATOR: section["command_separator"] = osgroup.command_separator config[osgroup.name] = section for server in osgroup.servers: if server.description: config[server.name] = {"description": server.description} self.log.debug("Saving configuration to {0}".format(self.path)) config.write(outfile=open(self.path, "w")) def match_os(self, name): for os in self.operating_systems: if os.name == name: return os raise ValueError("Unknown OS: {0}".format(name))
def __init__(self, osgroup, name, description=None): self.log = Logger().default_stream self.osgroup = osgroup self.name = name self.description = description
def __init__(self, authorized_keys=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.__parse_user_keyfiles() self.authorized_keys = AuthorizedKeys(authorized_keys)
class UserSSHKeys(dict): """ List of user configured SSH keys to process """ def __init__(self, authorized_keys=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.__parse_user_keyfiles() self.authorized_keys = AuthorizedKeys(authorized_keys) def __parse_user_keyfiles(self): """ Parses any normal user keyfiles in ~/.ssh directory automatically. These keys are marked by default not to be automatically loaded: to enable, add key path to ~/.ssh/sshkeys.conf """ user_sshdir = os.path.expanduser('~/.ssh') if not os.path.isdir(user_sshdir): return paths = [] for filename in os.listdir(user_sshdir): if filename in SSH_CONFIG_FILES or os.path.splitext(filename)[1] != '.pub': continue path = os.path.join(user_sshdir, filename) if os.path.isfile(path): paths.append(path) for path in paths: try: sshkey = SSHKeyFile(self, path) except SSHKeyError as e: self.log.debug(e) continue self[sshkey.path] = sshkey def read_config(self, path): """ Read a key configuration file, containing list of keys outside ~/.ssh directory to process. """ if not os.path.isfile(path): raise SSHKeyError('No such file: {0}'.format(path)) try: for l in [l.rstrip() for l in open(path, 'r').readlines()]: sshkey = SSHKeyFile(self, os.path.expandvars(os.path.expanduser(l))) if sshkey.path not in self.keys(): self[sshkey.path] = sshkey self[sshkey.path].autoload = True except IOError as e: raise SSHKeyError('Error loading {0}: {1}'.format(path, e)) except OSError as e: raise SSHKeyError('Error loading {0}: {1}'.format(path, e)) @property def loaded_keys(self): """ Return fingerprint, key mapping of keys loaded to ssh-agent """ keys = {} cmd = ['ssh-add', '-l'] p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) (stdout, stderr) = [x.decode('utf-8') for x in p.communicate()] if p.returncode == 1: l = stdout.split('\n')[0].strip() if l == 'The agent has no identities.': return keys if p.returncode != 0: raise SSHKeyError('Error listing loaded SSH agent keys') for l in [l.rstrip() for l in stdout.split('\n')[:-1]]: data = parse_public_key_line_pattern(l) if not data: raise SSHKeyError('Error parsing agent key list line {0}'.format(l)) keys[data['fingerprint']] = data return keys @property def available(self): return [k for k in self.values() if k.available] def keys(self): """ Return key filenames as sorted list """ return sorted(super(UserSSHKeys, self).keys()) def items(self): """ Return (filename, key) value pairs sorted with self.keys() """ return [(k, self[k]) for k in self.keys()] def values(self): """ Return keys sorted with self.keys() """ return [self[k] for k in self.keys()] def fix_permissions(self, directory_permissions=SSH_DIR_PERMS, file_permissions=SSH_FILE_PERMS): """ Fix permissions in ~/.ssh to match permissions set by parameters. All files must be writable by user to use this function. Default values: directory_permissions Directory permissions, by default '0700' file_permissions File permissions, bt default '0600' """ ssh_dir = os.path.expanduser('~/.ssh') dperm = int(directory_permissions, 8) fperm = int(file_permissions, 8) if not os.path.isdir(ssh_dir): self.log.debug('No such directory: {0}'.format(ssh_dir)) return for (root, dirs, files) in os.walk(ssh_dir): if stat.S_IMODE(os.stat(root).st_mode) != dperm: self.log.debug('Fixing permissions for directory {0}'.format(root)) os.chmod(root, dperm) for f in [os.path.join(root, f) for f in files]: if stat.S_IMODE(os.stat(f).st_mode) != fperm: self.log.debug('Fixing permissions for file {0}'.format(f)) os.chmod(f, fperm) def load_keys(self, keys): """Load given keys Load given keys to SSH agent. Checks if key was already loaded and skips if it was. Keys can be either paths or SSHKeyFile instances. """ paths = [] for key in keys: if isinstance(key, SSHKeyFile): if not key.is_loaded: paths.append(key.path) elif isinstance(key, str): paths.append(key) if paths: self.log.debug('Loading {0:d} keys to SSH agent'.format(len(paths))) cmd = [ 'ssh-add'] + paths p = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) p.wait() else: self.log.debug('All SSH keys were already loaded to SSH agent')
class OIDPrefix(object): """Sortable OID base class Base class for OID prefixes, sortable in OID order """ def __init__(self, oid): self.log = Logger('snmp').default_stream self.oid = self.__format_oid__(oid) self.oid_string = self.__format_oid_string__(oid) self._parent = None self._next = None def __format_oid_string__(self, oid): return '.%s' % '.'.join(str(x) for x in self.__format_oid__(oid)) def __format_oid__(self, oid): """Format OID Format OID string to validated OID list Return list of integeres """ if isinstance(oid, basestring): try: oid = oid.strip('.').split('.') except ValueError: raise ValueError('Invalid OID: %s' % oid) return self.__validate_oid__(oid) def __validate_oid__(self, oid): """Validate OID and return Validate list of integers to be a valid OID Returns value if OK """ try: oid = [int(x) for x in oid] for x in oid: if x<=0: raise ValueError except ValueError: raise ValueError('Invalid OID: %s' % oid) return oid def __invalid__(self, message): """Invalid request Log error and return None """ self.log.debug('%s' % message) return None def __cmp__(self, other): """Compare oid prefix items Compares OID prefixes """ if isinstance(other, basestring): oid = self.__format_oid__(other) elif isinstance(other, OIDPrefix): oid = other.oid else: raise ValueError('Error comparing OIDPrefix to %d' % type(other)) if len(oid) != len(self.oid): return cmp(len(self.oid), len(oid)) for i in range(0, len(oid)): if oid[i] != self.oid[i]: return cmp(self.oid[i], oid[i]) return cmp(self.oid, oid) def __eq__(self, oid): return self.__cmp__(oid) == 0 def __ne__(self, oid): return self.__cmp__(oid) != 0 def __lt__(self, oid): return self.__cmp__(oid) < 0 def __le__(self, oid): return self.__cmp__(oid) <= 0 def __gt__(self, oid): return self.__cmp__(oid) > 0 def __ge__(self, oid): return self.__cmp__(oid) >= 0 @property def get_response(self): return None @property def next_response(self): return None @property def parent(self): return self._parent @parent.setter def parent(self, value): self._parent = value @property def next(self): return self._next @next.setter def next(self, value): self._next = value def match(self, other): """Match other OID Match OID prefixes. Default function checks if self.oid == other.oid Override in child class (for example, trees match tree prefix) """ return self == other
def __init__(self, path=DEFAULT_AUTHORIZED_KEYS): self.log = Logger().default_stream self.path = path self.load()
class SNMPAgent(Script): """SNMP Agent implementation SNMP agent daemon for net-snmpd pass_persist mode Example usage: agent = SNMPAgent('.1.2.3.4') tree = agent.register_tree('1.2.3.4.1') tree.register('1.2.3.4.1.1', 'integer', 1) tree.register('1.2.3.4.1.2', 'integer', 2) agent.register_tree('1.2.3.4.2') agent.register('1.2.3.4.2.1', 'integer', 1) agent.register('1.2.3.4.2.4', 'integer', 4) agent.register('1.2.3.4.2.3', 'integer', 3) agent.register('1.2.3.4.2.2', 'integer', 2) tree = agent.register_tree('1.2.3.4.3') tree.register('1.2.3.4.3.1.1', 'integer', 1) tree.add_values('string', [x for x in string.digits[2:]]) tree = agent.register_tree('1.2.3.4.4') tree.add_prefix_map([ { 'oid': '1.2.3.4.4.1', 'key': 'string', 'values': [x for x in string.letters[:10]]}, { 'oid': '1.2.3.4.4.2', 'key': 'integer', 'values': [x for x in string.digits[:10]]}, ]) agent.run() """ def __init__(self, oid, reload_interval=60): Script.__init__(self) self.log = Logger('snmp').default_stream self.reload_interval = reload_interval self.last_reload = None self.add_argument('-g', '--get', help='SNMP GET request') self.add_argument('-n', '--next', help='SNMP GET request') self.add_argument('-t', '--tree', action='store_true', help='Show OID tree') self.tree = Tree(oid) def __SIGHUP__(self, signum, frame): """ Signal handler to reload configuration. Note this requires also the IOError processing in main input loop below """ self.log.debug('Reloading from signal') self.reload() def register_tree(self, oid): return self.tree.add(Tree(oid)) def register(self, oid, key, value): return self.tree.add(Item(oid, key, value)) def GET(self, oid): """GET from agent Return given OID from agent tree or None """ try: return self.tree.GET(oid) except SNMPError, emsg: self.log.debug(emsg) return None
class OperatingSystemGroup(object): """ Group of operating systems in configuration file """ def __init__(self, config, name, **kwargs): self.log = Logger().default_stream self.name = name self.description = name self.connect_command = DEFAULT_CONNECT_COMMAND self.command_separator = DEFAULT_COMMAND_SEPARATOR self.update_commands = [] self.servers = [] for k in ( 'description', 'command_separator', ): if k in kwargs: setattr(self, k, kwargs[k]) if 'connect' in kwargs: self.connect_command = kwargs['connect'] if 'commands' in kwargs: self.update_commands = kwargs['commands'] if 'servers' in kwargs: names = kwargs['servers'] if not isinstance(names, list): names = [names] for name in names: self.servers.append(Server(self, name)) self.modified = False def __repr__(self): return '%s: %s (%d servers)' % (self.name,self.description,len(self.servers)) @property def command_separator(self): return self._command_separator @command_separator.setter def command_separator(self, value): self._command_separator = value self.modified = True @property def connect_command(self): return self._connect_command @connect_command.setter def connect_command(self, value): if isinstance(value, basestring): value = value.split() self._connect_command = value self.modified = True @property def update_command(self): return self._update_commands @update_command.setter def update_commands(self, value): if isinstance(value, basestring): value = [value] self._update_commands = value self.modified = True @property def descripion(self): return self._descripion @descripion.setter def descripion(self, value): self._descripion = value self.modified = True def add_server(self, name): if name in [s.name for s in self.servers]: self.log.debug('Error adding: server already in group: %s' % name) return self.servers.append(Server(self, name)) self.modified = True def remove_server(self,name): try: server = [s for s in self.servers if s.name==name][0] self.servers.remove(server) self.modified = True except IndexError: self.log.debug('Error removing: server not in group: %s' % name) return
def __init__(self, path=None): self.defaults = {} self.log = Logger().default_stream self.path = path is not None and path or os.path.expanduser('~/.ssh/config') self.reload()
class Server(object): """ Configuration for one server """ def __init__(self, osgroup, name, description=None): self.log = Logger().default_stream self.osgroup = osgroup self.name = name self.description = description def __repr__(self): if self.description is not None: return '%s (%s)' % (self.name, self.description) else: return '%s' % self.name def __cmp__(self, other): if isinstance(other, basestring): return cmp(self.name, other) return cmp(self, other) def __eq__(self, other): return self.__cmp__(other) == 0 def __ne__(self, other): return self.__cmp__(other) != 0 def __gt__(self, other): return self.__cmp__(other) > 0 def __gte__(self, other): return self.__cmp__(other) >= 0 def __lt__(self, other): return self.__cmp__(other) < 0 def __lte__(self, other): return self.__cmp__(other) <= 0 @property def connect_command(self): return [x=='SERVER' and self.name or x for x in self.osgroup.connect_command] def check_output(self, command): """ Wrapper to execute and check output of a command without interactive console actions Splits string automatically to words so pass a list if you need to escape stuff properly. """ if not isinstance(command, list): command = [command] cmd = self.connect_command + command p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) (stdout,stderr) = p.communicate() return p.returncode, stdout.rstrip('\n'), stderr.rstrip('\n') def shell(self, command): """ Wrapper to run interactive shell command on the host, using current terminal for stdin, stdout and stderr Splits string automatically to words so pass a list if you need to escape stuff properly. Returns command return code """ if not isinstance(command, list): command = [command] cmd = self.connect_command + command p = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) p.wait() return p.returncode def update(self): """ Wrapper to call correct update commands for this host """ if not self.osgroup.update_commands: self.log.debug('No update commands for OS %s' % self.osgroup.name) return self.log.debug("Running: %s '%s'" % ( ' '.join(self.connect_command), self.osgroup.command_separator.join(self.osgroup.update_commands) )) cmd = self.connect_command + [ self.osgroup.command_separator.join(self.osgroup.update_commands) ] p = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) p.wait() return p.returncode def remove(self): """Remove from configuration Remove this server from configuration """ return self.osgroup.remove_server(self)
import json import logging from dateutil.parser import parse as dateparser from seine.whois import WhoisError from seine.address import IPv4Address, IPv6Address from systematic.log import Logger URL_TEMPLATE = 'http://whois.arin.net/rest/ip/%(address)s' DATE_FIELD_MAP = { 'registered': 'registrationDate', 'updated': 'updateDate', } logger = Logger().default_stream class ARINNetBlock(object): """Netblock entry Netblock entry in ARINReverseIP results """ def __init__(self, reverse, data): self.reverse = reverse self.description = data['description']['$'] self.type = data['type']['$'] self.mask = int(data['cidrLength']['$']) if reverse.address_format == IPv4Address: self.start = IPv4Address(data['startAddress']['$'])
class Script(object): """ Class for common CLI tool script """ def __init__(self, name=None, description=None, epilog=None, debug_flag=True): self.name = os.path.basename(sys.argv[0]) setproctitle('%s %s' % (self.name, ' '.join(sys.argv[1:]))) signal.signal(signal.SIGINT, self.SIGINT) reload(sys) sys.setdefaultencoding('utf-8') if name is None: name = self.name # Set to True to avoid any messages from self.message to be printed self.silent = False self.logger = Logger(self.name) self.log = self.logger.default_stream self.subcommand_parser = None self.parser = argparse.ArgumentParser( prog=name, description=description, formatter_class=argparse.RawTextHelpFormatter, epilog=epilog, add_help=True, conflict_handler='resolve', ) if debug_flag: self.parser.add_argument('--debug', action='store_true', help='Show debug messages') self.parser.add_argument('--insecure', action='store_false', help='No HTTPS certificate validation') self.parser.add_argument('-B', '--browser', choices=('chrome','chromium','firefox'), help='Browser for cookie stealing' ) def SIGINT(self, signum, frame): """ Parse SIGINT signal by quitting the program cleanly with exit code 1 """ for t in filter(lambda t: t.name!='MainThread', threading.enumerate()): t.stop() for t in filter(lambda t: t.name!='MainThread', threading.enumerate()): t.join() self.exit(1) def wait(self, poll_interval=1): """ Wait for running threads to finish. Poll interval is time to wait between checks for threads """ while True: active = filter(lambda t: t.name!='MainThread', threading.enumerate()) if not len(active): break self.log.debug('Waiting for %d threads' % len(active)) time.sleep(poll_interval) def exit(self, value=0, message=None): """ Exit the script with given exit value. If message is not None, it is printed on screen. """ if isinstance(value, bool): if value: value = 0 else: value = 1 else: try: value = int(value) if value < 0 or value > 255: raise ValueError except ValueError: value = 1 if message is not None: self.message(message) for t in filter(lambda t: t.name!='MainThread', threading.enumerate()): t.stop() while True: active = filter(lambda t: t.name!='MainThread', threading.enumerate()) if not len(active): break time.sleep(1) sys.exit(value) def message(self, message): if self.silent: return sys.stdout.write('%s\n' % message) def error(self, message): sys.stderr.write('%s\n' % message) def add_subcommand(self, command): """Add a subcommand parser instance Register named subcommand parser to argument parser Subcommand parser must be an instance of ScriptCommand class. Example usage: class ListCommand(ScriptCommand): def run(self, args): print 'Listing stuff' parser.add_subcommand(ListCommand('list', 'List stuff from script')) """ if self.subcommand_parser is None: self.subcommand_parser = self.parser.add_subparsers( dest='command', help='Please select one command mode below', title='Command modes' ) self.subcommands = {} if not isinstance(command, ScriptCommand): raise ScriptError('Subcommand must be a ScriptCommand instance') parser = self.subcommand_parser.add_parser( command.name, help=command.short_description, description=command.description, epilog=command.epilog, formatter_class=argparse.RawTextHelpFormatter, ) self.subcommands[command.name] = command command.script = self return parser def usage_error(self, *args, **kwargs): return self.parser.error(*args, **kwargs) def add_argument(self, *args, **kwargs): """ Shortcut to add argument to main argumentparser instance """ self.parser.add_argument(*args, **kwargs) def parse_args(self): """ Call parse_args for parser and check for default logging flags """ args = self.parser.parse_args() if hasattr(args, 'debug') and getattr(args, 'debug'): self.logger.set_level('DEBUG') elif hasattr(args, 'quiet') and getattr(args, 'quiet'): self.silent = True elif hasattr(args, 'verbose') and getattr(args, 'verbose'): self.logger.set_level('INFO') if self.subcommand_parser is not None: self.subcommands[args.command].run(args) return args def execute(self, args, dryrun=False): """ Default wrapper to execute given interactive shell command with standard stdin, stdout and stderr """ if isinstance(args, basestring): args = args.split() if not isinstance(args, list): raise ValueError('Execute arguments must be a list') if dryrun: self.log.debug('would execute: %s' % ' '.join(args)) return 0 p = Popen(args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr) p.wait() return p.returncode def check_output(self, args): """ Wrapper for subprocess.check_output to be executed in script context """ if isinstance(args, basestring): args = [args] try: return check_output(args) except IOError, (ecode, emsg): raise ScriptError(emsg) except OSError, (ecode, emsg): raise ScriptError(emsg)
def __init__(self, path=DEFAULT_KNOWN_HOSTS, fingerprint_hash=None): self.log = Logger().default_stream self.path = path self.fingerprint_hash = fingerprint_hash self.load()
class Script(object): """ Class for common CLI tool script """ def __init__(self, name=None, description=None, epilog=None, debug_flag=True): self.name = os.path.basename(sys.argv[0]) signal.signal(signal.SIGINT, self.SIGINT) if has_setproctitle: setproctitle('{0} {1}'.format(self.name, ' '.join(sys.argv[1:]))) if sys.version_info.major < 3: reload(sys) # noqa sys.setdefaultencoding('utf-8') if name is None: name = self.name # Set to True to avoid any messages from self.message to be output self.silent = False self.logger = Logger(self.name) self.log = self.logger.default_stream self.parser = argparse.ArgumentParser( prog=name, description=description, formatter_class=argparse.RawTextHelpFormatter, epilog=epilog, add_help=True, conflict_handler='resolve', ) if debug_flag: self.parser.add_argument('--debug', action='store_true', help='Show debug messages') self.subcommand_parser = None def SIGINT(self, signum, frame): """ Parse SIGINT signal by quitting the program cleanly with exit code 1 """ for t in [t for t in threading.enumerate() if t.name != 'MainThread']: if hasattr(t, 'stop') and callable(t.stop): t.stop() for t in [t for t in threading.enumerate() if t.name != 'MainThread']: t.join() self.exit(1) def wait(self, poll_interval=1): """ Wait for running threads to finish. Poll interval is time to wait between checks for threads """ while True: active = [ t for t in threading.enumerate() if t.name != 'MainThread' ] if not len(active): break self.log.debug('Waiting for {0:d} threads'.format(len(active))) time.sleep(poll_interval) def exit(self, value=0, message=None): """ Exit the script with given exit value. If message is not None, it is output to stdout """ if isinstance(value, bool): if value: value = 0 else: value = 1 else: try: value = int(value) if value < 0 or value > 255: raise ValueError except ValueError: value = 1 if message is not None: self.message(message) for t in [t for t in threading.enumerate() if t.name != 'MainThread']: if hasattr(t, 'stop') and callable(t.stop): t.stop() while True: active = [ t for t in threading.enumerate() if t.name != 'MainThread' ] if not len(active): break time.sleep(0.1) sys.exit(value) def message(self, message): if self.silent: return sys.stdout.write('{0}\n'.format(message)) def error(self, message): sys.stderr.write('{0}\n'.format(message)) def add_subcommand(self, command): """Add a subcommand parser instance Register named subcommand parser to argument parser Subcommand parser must be an instance of ScriptCommand class. Example usage: class ListCommand(ScriptCommand): def run(self, args): self.message('Listing stuff') parser.add_subcommand(ListCommand('list', 'List stuff from script')) """ if self.subcommand_parser is None: self.subcommand_parser = self.parser.add_subparsers( dest='command', help='Please select one command mode below', title='Command modes') self.subcommands = {} if not isinstance(command, ScriptCommand): raise ScriptError('Subcommand must be a ScriptCommand instance') parser = self.subcommand_parser.add_parser( command.name, help=command.short_description, description=command.description, epilog=command.epilog, formatter_class=argparse.RawTextHelpFormatter, ) self.subcommands[command.name] = command command.script = self if callable(getattr(command, '__register_arguments__', None)): command.__register_arguments__(parser) return parser def usage_error(self, *args, **kwargs): return self.parser.error(*args, **kwargs) def add_argument(self, *args, **kwargs): """ Shortcut to add argument to main argumentparser instance """ self.parser.add_argument(*args, **kwargs) def __process_args__(self, args): """Process args Process args from parse_*args CalledProcessError """ if getattr(args, 'debug', None): self.logger.set_level('DEBUG') elif getattr(args, 'quiet', None): self.silent = True elif getattr(args, 'verbose', None): self.logger.set_level('INFO') if self.subcommand_parser is not None and args.command is not None: if hasattr(self.subcommands[args.command], 'parse_args'): args = self.subcommands[args.command].parse_args(args) self.subcommands[args.command].run(args) return args def parse_args(self): """ Call parse_args for parser and check for default logging flags """ return self.__process_args__(self.parser.parse_args()) def parse_known_args(self): """ Call parse_args for parser and check for default logging flags """ args, other_args = self.parser.parse_known_args() args = self.__process_args__(args) return args, other_args def run(self): """ Run script, parsing arguments and running subcommands This expects subcommands have been registered and exists if not This simply runs self.parse_args() not expecting to do anything with returned values since the subcommand is run. """ if self.subcommand_parser is None: self.exit(1, 'Command defines no subcommands') args = self.parse_args() if args.command is None: self.exit(1, 'No command selected') def execute(self, args, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, dryrun=False): """ Default wrapper to execute given interactive shell command with standard stdin, stdout and stderr """ if isinstance(args, str): args = args.split() if not isinstance(args, list): raise ValueError('Execute arguments must be a list') if dryrun: self.log.debug('would execute: {0}'.format(' '.join(args))) return 0 p = Popen(args, stdin=stdin, stdout=stdout, stderr=stderr) p.wait() return p.returncode def check_output(self, args): """ Wrapper for subprocess.check_output to be executed in script context """ if isinstance(args, str): args = [args] try: return check_output(args) except IOError as e: raise ScriptError(e) except OSError as e: raise ScriptError(e) except CalledProcessError as e: raise ScriptError(e)
def __init__(self, resultset, name): self.log = Logger().default_stream self.resultset = resultset self.name = name self.ansible_facts = {}