async def enum(api, args): '''Enumerates challenges ''' found = False for challenge in api.enum(tags=args.tags, categories=args.categories, slug=args.slug): slug, conf = challenge['slug'], challenge['conf'] if not found: found = True print("challenges:") if conf is None: app_log.error( f"configuration missing. Run `mkctf configure -s {slug}`") continue chall_entry = f"{TAB}- {slug} [{conf['category'].upper()}]" color = 'green' if conf['enabled'] else 'red' chall_entry = format_text(chall_entry, color, attrs=['bold']) del conf['slug'] del conf['enabled'] del conf['category'] description = challenge['description'] or format_text( 'empty description', 'red', attrs=['bold']) chall_details = format_dict2str(conf) chall_details += "\n+ description:" chall_details += indent(f"\n{HSEP}\n{description}\n{HSEP}", TAB) print(chall_entry) if not args.summarize: print(indent(chall_details[1:], TAB * 2)) if not found: app_log.warning("no challenge found.") return found
def readline(prompt, allow_empty=False, expect_digit=False, default=None): '''Reads one line ''' value = '' if not isinstance(prompt, str): raise ValueError("prompt argument must be a string.") if default is not None: if expect_digit: if not isinstance(default, int): raise ValueError("default argument must be an integer.") elif not isinstance(default, str): raise ValueError("default argument must be an string.") prompt = prompt.strip() prompt = f"{prompt} [default={default}] " full_prompt = get_prompt(prompt) while True: value = input(full_prompt) if len(value) > 0: if expect_digit and not INT_RE.fullmatch(value): app_log.error("answer must be a digit.") continue break if default is not None: return default if allow_empty: return None app_log.error("empty answer is not allowed.") if expect_digit: value = int(value, 0) return value
def choose_one(prompt, choices, default=None, allow_custom=False): '''Elect one element among a collection You can also allow tht user to enter a custom value ''' selection = default if not isinstance(choices, list): # do not check number of elements raise ValueError("choices must be a list") has_default = default is not None if has_default and not default in choices: raise ValueError("default value must be one of choices.") print(get_prompt(prompt)) k = 0 for choice in choices: default_str = '' if choice == default: default_str = ' [default]' print(f"\t{k:02d}: {choices[k]}{default_str}") k += 1 if allow_custom: print(f"\t{k:02d}: custom value") while True: choice = readline("enter a number: ", allow_empty=has_default, expect_digit=True) if choice is None: return default if choice >= 0 or choice <= k: break app_log.error(f"input must be in [0,{k}]") if allow_custom and choice == k: selection = readline("enter custom value: ") else: selection = choices[choice] return selection
async def enum(api, args): '''Enumerates challenges ''' found = False print("challenges:") for challenge in api.enum(args.tags, args.slug): slug, conf = challenge['slug'], challenge['conf'] found = True if conf is None: app_log.error( f"configuration missing. Run `mkctf configure -s {slug}`") continue static = ' [STANDALONE]' if conf['standalone'] else '' chall_entry = f"{TAB}- {slug}{static}" color = 'green' if conf['enabled'] else 'red' chall_entry = format_text(chall_entry, color, attrs=['bold']) del conf['enabled'] del conf['standalone'] del conf['slug'] description = challenge['description'] or format_text( 'empty description', 'red', attrs=['bold']) text = format_dict2str(conf) text += "\n+ description:" text += indent(f"\n{HSEP}\n{description}\n{HSEP}", TAB) print(chall_entry) print(indent(text[1:], TAB * 2)) if not found: app_log.warning("no challenge found matching given constraints.") return found
def _save_conf(self): '''Save repository configuration to disk ''' if not self._conf.validate(throw=False): app_log.error("save operation aborted: invalid configuration") return False self._conf.save(self._conf_path) return True
def create_chall(self, override_conf=None): '''Creates a challenge ''' final_conf = override_conf if not final_conf: wizard = ChallengeConfigurationWizard(self.conf) if not wizard.show(): return False final_conf = wizard.result chall_path = self.challenges_dir.joinpath(final_conf.slug) if chall_path.is_dir(): app_log.error(f"{final_conf.slug} already exists") return False chall = Challenge(self, self.challenges_dir.joinpath(final_conf.slug), final_conf) return chall.init()
def export(self, export_dir, include_disabled): '''Export the challenge Creates a gzipped tar archive containing all of the challenge "exportable" files ''' if not include_disabled and not self.conf.enabled: app_log.warning(f"export ignored {self.conf.slug} (disabled)") return app_log.info(f"exporting {self.conf.slug}...") archive_name = self.conf.static_url.split('/')[-1] if not archive_name: app_log.error( f"export ignored {self.conf.slug} (invalid/empty static_url)") app_log.error( f"running `mkctf-cli update-meta` should be enough to fix this issue." ) return archive_path = export_dir.joinpath(archive_name) checksum_file = ChecksumFile() with tarfile.open(str(archive_path), 'w:gz') as arch: for directory in self.repo.conf.directories(self.conf.category, public_only=True): dir_path = self.path.joinpath(directory) for entry in scandir(dir_path): entry_path = Path(entry.path) if entry_path.is_dir(): app_log.warning( f"export ignored {entry_path} within {self.conf.slug} (directory)" ) continue checksum_file.add(entry_path) app_log.debug(f"adding {entry_path} to archive...") arch.add(str(entry_path), arcname=entry.name) with tempfile.NamedTemporaryFile('w') as tmpfile: tmpfile.write(checksum_file.content) tmpfile.flush() app_log.debug(f"adding checksum.sha256 to archive...") arch.add(tmpfile.name, arcname='checksum.sha256') arch_checksum_file = ChecksumFile() arch_checksum_file.add(archive_path) export_dir.joinpath(f'{archive_name}.sha256').write_text( arch_checksum_file.content) return archive_path
def readline(prompt, empty=False, digit=False, default=None): '''Reads one line ''' if isinstance(default, tuple): default_str = default[1] default = default[0] else: default_str = default value = '' while True: value = input(build_prompt(prompt, default_str)) if len(value) > 0: if digit and not INT_RE.fullmatch(value): app_log.error("answer must be a digit.") continue break if default is not None: return default if empty: return None app_log.error("empty answer is not allowed.") if digit: value = int(value, 0) return value