def database_attach(self, args):
        """ Attach to the selected database """
        locations = [str(x) for x in self.get_databases()] + ["create new database"]

        location = self.select(locations)

        if location == "create new database":
            location = self.read_input(
                style("new database name? (recommend something unique for this target)\n-> ", fg="bright_white")
            )

            new_location = str(Path(defaults.get("database-dir")) / location)
            index = sorted([new_location] + locations[:-1]).index(new_location) + 1

            self.db_mgr = DBManager(db_location=new_location)

            self.poutput(style(f"[*] created database @ {new_location}", fg="bright_yellow"))

            location = new_location

        else:
            index = locations.index(location) + 1
            self.db_mgr = DBManager(db_location=location)

        self.add_dynamic_parser_arguments()

        self.poutput(
            style(f"[+] attached to sqlite database @ {Path(location).expanduser().resolve()}", fg="bright_green")
        )
        self.prompt = f"[db-{index}] {DEFAULT_PROMPT}"
Beispiel #2
0
    def _finalize_tool_action(self, tool: str, tool_dict: dict, return_values: List[int], action: ToolActions):
        """ Internal helper to keep DRY

        Args:
            tool: tool on which the action has been performed
            tool_dict: tools dictionary to save
            return_values: accumulated return values of subprocess calls
            action: ToolAction.INSTALL or ToolAction.UNINSTALL
        """
        verb = ["install", "uninstall"][action.value]

        if all(x == 0 for x in return_values):
            # all return values in retvals are 0, i.e. all exec'd successfully; tool action has succeeded

            self.poutput(style(f"[+] {tool} {verb}ed!", fg="bright_green"))

            tool_dict[tool]["installed"] = True if action == ToolAction.INSTALL else False
        else:
            # unsuccessful tool action

            tool_dict[tool]["installed"] = False if action == ToolAction.INSTALL else True

            self.poutput(
                style(
                    f"[!!] one (or more) of {tool}'s commands failed and may have not {verb}ed properly; check output from the offending command above...",
                    fg="bright_red",
                    bold=True,
                )
            )
Beispiel #3
0
def printlinks(msql, mp, listElements, bGroup, bFundamental):
    dictTypesSymbols = dict(zip(md.listLinkTypes, md.listLinkTypesSymbols))
    dictTypeColours = dict(zip(md.listElementTypes,
                               md.listElementTypesColours))
    listLinks = ml.computeLinks(msql, listElements)
    if (bGroup):
        listLinks = ml.groupLinks(listLinks)

    #print table header
    t = PrettyTable(['Name', 'Source', 'Type', 'Destination'])
    t.align = "l"
    t.set_style(PLAIN_COLUMNS)
    t.header = False

    for linkinfo in listLinks:
        link = msql.getLinkPerId(linkinfo.id)

        if ((bFundamental == True) and (linkinfo.type == "fundamental")
                or (bFundamental == False)):
            startElement = msql.getElementPerId(linkinfo.start_element_id)
            endElement = msql.getElementPerId(linkinfo.end_element_id)

            t.add_row([
                ansi.style(mp.getRelativePath(startElement[md.ELEMENT_PATH]),
                           fg=dictTypeColours[startElement[md.ELEMENT_TYPE]]),
                dictTypesSymbols[link[md.LINK_TYPE]],
                ansi.style(mp.getRelativePath(endElement[md.ELEMENT_PATH]),
                           fg=dictTypeColours[endElement[md.ELEMENT_TYPE]]),
                linkinfo.name
            ])

    if listLinks:
        print(t)
Beispiel #4
0
    def do_download(self, args):
        # Error checking
        my_file = pathlib.Path('../server/uploads/' + args.file)
        if my_file.exists() is False:
            output_error = ansi.style(
                "[-] The file does not exist in <root>/server/uploads/ !",
                fg='red',
                bold=True)
            self.poutput(output_error)

        target_list = args.target.split(",")

        # Order the bots to download file from the server
        for target in target_list:
            url = URL + '/bot/' + target + '/push'
            masterkey = MASTERKEY
            cmd = "download " + args.file + " " + args.destination

            data = {'masterkey': masterkey, 'cmd': cmd}
            res = requests.post(url, data=data, verify=False)

            output_push = ansi.style("[+] Download file staged for: " + target,
                                     fg='green',
                                     bold=True)

            self.poutput(output_push)
            self.poutput(res.text)
Beispiel #5
0
def test_style_color_not_exist():
    base_str = HELLO_WORLD

    with pytest.raises(ValueError):
        ansi.style(base_str, fg='fake', bg='green')

    with pytest.raises(ValueError):
        ansi.style(base_str, fg='blue', bg='fake')
    def database_detach(self, args):
        """ Detach from the currently attached database """
        if self.db_mgr is None:
            return self.poutput(style(f"[!] you are not connected to a database", fg="magenta"))

        self.db_mgr.close()
        self.poutput(style(f"[*] detached from sqlite database @ {self.db_mgr.location}", fg="bright_yellow"))
        self.db_mgr = None
        self.prompt = DEFAULT_PROMPT
    def database_list(self, args):
        """ List all known databases """
        try:
            next(self.get_databases())
        except StopIteration:
            return self.poutput(style(f"[-] There are no databases.", fg="bright_white"))

        for i, location in enumerate(self.get_databases(), start=1):
            self.poutput(style(f"   {i}. {location}"))
Beispiel #8
0
 def tools_list(self, args):
     """ List status of pipeline tools """
     for key, value in self._get_dict().items():
         status = [
             style(":Missing:", fg="bright_magenta"),
             style("Installed", fg="bright_green")
         ]
         self.poutput(
             style(
                 f"[{status[value.get('installed')]}] - {value.get('path') or key}"
             ))
Beispiel #9
0
    def _luigi_pretty_printer(self, stderr):
        """ Helper to clean up the VERY verbose luigi log messages that are normally spewed to the terminal. """

        output = stderr.readline()

        if not output:
            return

        output = output.decode()

        if "===== Luigi Execution Summary =====" in output:
            # header that begins the summary of all luigi tasks that have executed/failed
            self.async_alert("")
            self.sentry = True

        # block below used for printing status messages
        if self.sentry:

            # only set once the Luigi Execution Summary is seen
            self.async_alert(style(output.strip(), fg="bright_blue"))
        elif output.startswith("INFO: Informed") and output.strip().endswith(
                "PENDING"):
            # luigi Task has been queued for execution

            words = output.split()

            self.async_alert(
                style(f"[-] {words[5].split('_')[0]} queued",
                      fg="bright_white"))
        elif output.startswith("INFO: ") and "running" in output:
            # luigi Task is currently running

            words = output.split()

            # output looks similar to , pid=3938074) running   MasscanScan(
            # want to grab the index of the luigi task running and use it to find the name of the scan (i.e. MassScan)
            scantypeidx = words.index("running") + 1
            scantype = words[scantypeidx].split("(", 1)[0]

            self.async_alert(
                style(f"[*] {scantype} running...", fg="bright_yellow"))
        elif output.startswith("INFO: Informed") and output.strip().endswith(
                "DONE"):
            # luigi Task has completed

            words = output.split()

            self.async_alert(
                style(f"[+] {words[5].split('_')[0]} complete!",
                      fg="bright_green"))
Beispiel #10
0
    def do_login(self, args):
        remoteserver = args.remote
        username = args.username
        password = args.password

        # TODO: Update the URL to have custom port numbers as well
        global URL
        URL = 'https://' + remoteserver

        auth_url = URL + '/auth'

        output_status = ansi.style('[DEBUG] Logging in as master...',
                                   fg='blue',
                                   bold=True)
        output_host = ansi.style('[DEBUG] ' + remoteserver,
                                 fg='blue',
                                 bold=True)
        output_username = ansi.style('[DEBUG] ' + username,
                                     fg='blue',
                                     bold=True)
        output_password = ansi.style('[DEBUG] ' + password,
                                     fg='blue',
                                     bold=True)

        self.poutput(output_host)
        self.poutput(output_username)
        self.poutput(output_password)

        # Send authentication request to /auth endpoint
        data = {'username': username, 'password': password}
        res = requests.post(auth_url, data, verify=False)
        response_data = res.json()

        # Authentication successful
        if response_data['result'] == 'SUCCESS':
            global MASTERKEY
            MASTERKEY = response_data['masterkey']
            output_success = ansi.style('[+] Successfully logged in.',
                                        fg='green',
                                        bold=True)
            output_success = ansi.style('[+] Retrieved Master key = ' +
                                        MASTERKEY,
                                        fg='green',
                                        bold=True)
            self.poutput(output_success)

            # Begin the refresh thread to automatically refresh botlist in server.
            refreshing = threading.Thread(target=refresh)
            refreshing.daemon = True
            refreshing.start()

        # Authentication failed
        else:
            output_failed_server = ansi.style(res.text, fg='red', bold=True)
            output_failed = ansi.style('[-] Authentication have failed.',
                                       fg='red',
                                       bold=True)
            self.poutput(output_failed_server)
            self.poutput(output_failed)
Beispiel #11
0
def printelements(msql, listElements):
    dictTypeColours = dict(zip(md.listElementTypes,
                               md.listElementTypesColours))
    strLine = ""
    for element in listElements:
        listDescendants = msql.getSonsPerId(element[md.ELEMENT_ID])
        if listDescendants:  #if the element has descendants, plot a (+)
            strLine += ansi.style(
                '(+)' + element[md.ELEMENT_NAME],
                fg=dictTypeColours[element[md.ELEMENT_TYPE]]) + "\t"
        else:
            strLine += ansi.style(
                element[md.ELEMENT_NAME],
                fg=dictTypeColours[element[md.ELEMENT_TYPE]]) + "\t"
    print(strLine)
Beispiel #12
0
    def do_broadcast(self, args):
        url = URL + '/bot/broadcast'
        masterkey = MASTERKEY
        cmd = args.command

        data = {'masterkey': MASTERKEY, 'cmd': cmd}

        res = requests.post(url, data=data, verify=False)
        response_data = res.json()

        output_broadcast = ansi.style('[+] Broadcast command has been staged.')

        if 'result' in response_data:
            self.poutput(
                ansi.style(response_data['result'], fg='green', bold=True))
Beispiel #13
0
    def check_scan_directory(self, directory):
        """ Determine whether or not the results-dir about to be used already exists and prompt the user accordingly.

        Args:
            directory: the directory passed to ``scan ... --results-dir``
        """
        directory = Path(directory)

        if directory.exists():
            term_width = shutil.get_terminal_size((80, 20)).columns

            warning_msg = (
                f"[*] Your results-dir ({str(directory)}) already exists. Subfolders/files may tell "
                f"the pipeline that the associated Task is complete. This means that your scan may start "
                f"from a point you don't expect. Your options are as follows:")

            for line in textwrap.wrap(warning_msg,
                                      width=term_width,
                                      subsequent_indent="    "):
                self.poutput(style(line, fg="bright_yellow"))

            option_one = (
                "Resume existing scan (use any existing scan data & only attempt to scan what isn't already done)"
            )
            option_two = "Remove existing directory (scan starts from the beginning & all existing results are removed)"
            option_three = "Save existing directory (your existing folder is renamed and your scan proceeds)"

            answer = self.select([("Resume", option_one),
                                  ("Remove", option_two),
                                  ("Save", option_three)])

            if answer == "Resume":
                self.poutput(
                    style("[+] Resuming scan from last known good state.",
                          fg="bright_green"))
            elif answer == "Remove":
                shutil.rmtree(Path(directory))
                self.poutput(
                    style("[+] Old directory removed, starting fresh scan.",
                          fg="bright_green"))
            elif answer == "Save":
                current = time.strftime("%Y%m%d-%H%M%S")
                directory.rename(f"{directory}-{current}")

                self.poutput(
                    style(
                        f"[+] Starting fresh scan.  Old data saved as {directory}-{current}",
                        fg="bright_green"))
Beispiel #14
0
def test_style_multi():
    base_str = HELLO_WORLD
    fg_color = 'blue'
    bg_color = 'green'
    ansi_str = ansi.FG_COLORS[fg_color] + ansi.BG_COLORS[bg_color] + ansi.BRIGHT + ansi.UNDERLINE_ENABLE + \
               base_str + ansi.FG_RESET + ansi.BG_RESET + ansi.NORMAL + ansi.UNDERLINE_DISABLE
    assert ansi.style(base_str, fg=fg_color, bg=bg_color, bold=True, underline=True) == ansi_str
Beispiel #15
0
    def do_push(self, args):
        target_list = args.target.split(",")

        # Push the command to multiple targets
        for target in target_list:
            #print("[DEBUG] target = ", target)
            url = URL + '/bot/' + target + '/push'
            masterkey = MASTERKEY
            cmd = args.command

            data = {'masterkey': masterkey, 'cmd': cmd}
            res = requests.post(url, data=data, verify=False)

            output_push = ansi.style("[+] Command staged for: " + target,
                                     fg='green',
                                     bold=True)

            self.poutput(output_push)
            self.poutput(res.text)

            # If command staging was successful, check the result page after TIMER.
            if 'result' in res.text:
                pushThread = threading.Thread(target=checkPushResult,
                                              args=(
                                                  URL,
                                                  MASTERKEY,
                                                  target,
                                                  cmd,
                                              ))
                pushThread.daemon = True
                pushThread.start()
Beispiel #16
0
    def __init__(self):
        super().__init__(use_ipython=True)

        banner = r"""

__   __    _     _   _      _                                       
\ \ / /   | |   | \ | |    | |                                      
 \ V /__ _| |__ |  \| | ___| |_                                     
  \ // _` | '_ \| . ` |/ _ \ __|                                    
  | | (_| | |_) | |\  |  __/ |_                                     
  \_/\__,_|_.__/\_| \_/\___|\__|                                    
                                                                    
                                                                    
___  ___          _              _____                       _      
|  \/  |         | |            /  __ \                     | |     
| .  . | __ _ ___| |_ ___ _ __  | /  \/ ___  _ __  ___  ___ | | ___ 
| |\/| |/ _` / __| __/ _ \ '__| | |    / _ \| '_ \/ __|/ _ \| |/ _ \
| |  | | (_| \__ \ ||  __/ |    | \__/\ (_) | | | \__ \ (_) | |  __/
\_|  |_/\__,_|___/\__\___|_|     \____/\___/|_| |_|___/\___/|_|\___|
                                                                    
                                                                    

        """
        self.intro = ansi.style(banner, fg='red', bold=True)

        # Allow access to your application in py and ipy via self
        self.locals_in_py = True

        # Set the default category name
        self.default_category = 'cmd2 Built-in Commands'

        # Should ANSI color output be allowed
        self.allow_ansi = ansi.ANSI_TERMINAL
Beispiel #17
0
    def __init__(self, args):
        shortcuts = dict(cmd2.DEFAULT_SHORTCUTS)
        # Run a quick script to get students with timed-out tests
        timed_out_cmd = (f"grep 'Timed out' {args['assgn_dir']}/candidates/*/report.txt | " +
                         f"sed -n 's/.*candidates\/\([a-zA-Z0-9\.]*\).*/\\1/p' | uniq")
        shortcuts.update({'timed-out': timed_out_cmd})

        # cmd2 doesn't allow dashes by default, so just alias them
        shortcuts.update({'upload-reports'  : 'upload_reports'})
        shortcuts.update({'upload-marks'    : 'upload_marks'})
        shortcuts.update({'delete-reports'  : 'delete_reports'})
        shortcuts.update({'set-status'      : 'set_status'})

        hist_file = os.path.join(CONFIG_DIR, "cli_history.dat")
        super().__init__(
            use_ipython=False, 
            shortcuts=shortcuts, 
            persistent_history_file=hist_file,
        )

    # Hide all built-in commands (still accessible)...
        to_hide = ['edit', 'macro', 'py', 'shortcuts', 'set', 'q',
                   'run_pyscript', 'run_script', 'alias', 'help',
                   'shell', 'history', 'quit']
        for cmd in to_hide:
            self.hidden_commands.append(cmd)

        self.args = args
        self.console = REPLConsole()
        self.marker = Marker(self.args, self.console)
        self.prompt = ansi.style('marker > ', fg="blue")
        self.default_to_shell = True
        self.students_list = []
        self.debug = True
        self.update_students_list()
Beispiel #18
0
def test_wrap_long_word():
    # Make sure words wider than column start on own line and wrap
    column_1 = Column("LongColumnName", width=10)
    column_2 = Column("Col 2", width=10)

    columns = [column_1, column_2]
    tc = TableCreator(columns)

    # Test header row
    header = tc.generate_row()
    assert header == ('LongColumn            \n'
                      'Name        Col 2     ')

    # Test data row
    row_data = list()

    # Long word should start on the first line (style should not affect width)
    row_data.append(ansi.style("LongerThan10", fg=ansi.fg.green))

    # Long word should start on the second line
    row_data.append("Word LongerThan10")

    row = tc.generate_row(row_data=row_data)
    expected = (ansi.RESET_ALL + ansi.fg.green + "LongerThan" + ansi.RESET_ALL + "  Word      \n"
                + ansi.RESET_ALL + ansi.fg.green + "10" + ansi.fg.reset + ansi.RESET_ALL + '        ' + ansi.RESET_ALL + '  LongerThan\n'
                '            10        ')
    assert row == expected
Beispiel #19
0
def test_column_creation():
    # No width specified, blank label
    c = Column("")
    assert c.width == 1

    # No width specified, label isn't blank but has no width
    c = Column(ansi.style('', fg=ansi.fg.green))
    assert c.width == 1

    # No width specified, label has width
    c = Column("short\nreally long")
    assert c.width == ansi.style_aware_wcswidth("really long")

    # Width less than 1
    with pytest.raises(ValueError) as excinfo:
        Column("Column 1", width=0)
    assert "Column width cannot be less than 1" in str(excinfo.value)

    # Width specified
    c = Column("header", width=20)
    assert c.width == 20

    # max_data_lines less than 1
    with pytest.raises(ValueError) as excinfo:
        Column("Column 1", max_data_lines=0)
    assert "Max data lines cannot be less than 1" in str(excinfo.value)
Beispiel #20
0
    def print_target_results(self, args):
        """ Display all Targets from the database, ipv4/6 and hostname """
        results = list()
        printer = self.ppaged if args.paged else self.poutput

        if args.type == "ipv4":
            targets = self.db_mgr.get_all_ipv4_addresses()
        elif args.type == "ipv6":
            targets = self.db_mgr.get_all_ipv6_addresses()
        elif args.type == "domain-name":
            targets = self.db_mgr.get_all_hostnames()
        else:
            targets = self.db_mgr.get_all_targets()

        for target in targets:
            if args.vuln_to_subdomain_takeover:
                tgt = self.db_mgr.get_or_create_target_by_ip_or_hostname(
                    target)
                if not tgt.vuln_to_sub_takeover:
                    # skip targets that aren't vulnerable
                    continue
                vulnstring = style("vulnerable", fg="green")
                vulnstring = f"[{vulnstring}] {target}"
                results.append(vulnstring)
            else:
                results.append(target)

        if results:
            printer("\n".join(results))
Beispiel #21
0
 def _generate_colored_prompt(self) -> str:
     """Randomly generates a colored prompt
     :return: the new prompt
     """
     fg_color = random.choice(list(ansi.FG_COLORS.keys()))
     bg_color = random.choice(list(ansi.BG_COLORS.keys()))
     return ansi.style(
         self.visible_prompt.rstrip(), fg=fg_color, bg=bg_color) + ' '
Beispiel #22
0
    def do_cleanup(self, args):
        try:
            if URL is None:
                self.poutput('URL is not set!')
        except Exception as e:
            output_loginfirst = ansi.style("\n\n!!! Login First !!!\n",
                                           fg='red',
                                           bg='blue',
                                           bold=True)
            self.poutput(output_loginfirst)

        url = URL + '/bot/cleanup'
        data = {'masterkey': MASTERKEY}
        res = requests.post(url, data=data, verify=False)

        output_cleanup = ansi.style(res.text, fg='green', bold=True)
        self.poutput(output_cleanup)
Beispiel #23
0
def test_style_aware_wcswidth():
    base_str = HELLO_WORLD
    ansi_str = ansi.style(base_str, fg='green')
    assert ansi.style_aware_wcswidth(HELLO_WORLD) == ansi.style_aware_wcswidth(
        ansi_str)

    assert ansi.style_aware_wcswidth('i have a tab\t') == -1
    assert ansi.style_aware_wcswidth('i have a newline\n') == -1
Beispiel #24
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     if not which("nmap"):
         raise RuntimeError(
             style("[!] nmap is not installed", fg="bright_red"))
     self.db_mgr = pipeline.models.db_manager.DBManager(
         db_location=self.db_location)
     self.results_subfolder = (Path(self.results_dir) /
                               "nmap-results").expanduser().resolve()
Beispiel #25
0
def printTreeLeaf(msql, element, strIndent, dictTypeColours):
    listSons = msql.getSonsPerId(element[md.ELEMENT_ID])
    print(
        ansi.style(strIndent + '└── ' + element[md.ELEMENT_NAME],
                   fg=dictTypeColours[element[md.ELEMENT_TYPE]]))
    strIndentSons = strIndent + "    "
    for elementSon in listSons:
        printTreeLeaf(msql, elementSon, strIndentSons, dictTypeColours)
    return
Beispiel #26
0
    def do_generate(self, args):
        self.poutput(
            ansi.style('[+] Compiling golang agent file...',
                       fg='green',
                       bold=True))

        ip = args.ip
        port = args.port
        windowsBool = args.windows

        #print(freeze)

        generator.goCompile(ip, port, windowsBool)

        self.poutput(
            ansi.style('[+] Generate command executed successfully.',
                       fg='green',
                       bold=True))
    def database_delete(self, args):
        """ Delete the selected database """
        locations = [str(x) for x in self.get_databases()]

        to_delete = self.select(locations)
        index = locations.index(to_delete) + 1

        Path(to_delete).unlink()

        if f"[db-{index}]" in self.prompt:
            self.poutput(style(f"[*] detached from sqlite database at {self.db_mgr.location}", fg="bright_yellow"))
            self.prompt = DEFAULT_PROMPT
            self.db_mgr.close()
            self.db_mgr = None

        self.poutput(
            style(f"[+] deleted sqlite database @ {Path(to_delete).expanduser().resolve()}", fg="bright_green")
        )
Beispiel #28
0
def test_widest_line():
    text = ansi.style('i have\n3 lines\nThis is the longest one', fg='green')
    assert ansi.widest_line(text) == ansi.style_aware_wcswidth(
        "This is the longest one")

    text = "I'm just one line"
    assert ansi.widest_line(text) == ansi.style_aware_wcswidth(text)

    assert ansi.widest_line('i have a tab\t') == -1
Beispiel #29
0
 def _stylize_prompt(_prompt: str, fg: Fg = Fg.DARK_GRAY) -> str:
     """
     Stylize a prompt with a foreground color.
     :param _prompt: The prompt to stylize.
     :param fg: The foreground color.
     :return: A cmd2 ansi style.
     :rtype: str
     """
     return ansi.style(_prompt, fg=fg)
Beispiel #30
0
def main(
    name,
    old_tools_dir=Path().home() / ".recon-tools",
    old_tools_dict=Path().home() / ".cache" / ".tool-dict.pkl",
    old_searchsploit_rc=Path().home() / ".searchsploit_rc",
):
    """ Functionified for testability """
    if name == "__main__":

        if old_tools_dir.exists() and old_tools_dir.is_dir():
            # want to try and ensure a smooth transition for folks who have used the pipeline before from
            # v0.8.4 and below to v0.9.0+
            print(
                style(
                    "[*] Found remnants of an older version of recon-pipeline.",
                    fg="bright_yellow"))
            print(
                style(
                    f"[*] It's {style('strongly', fg='red')} advised that you allow us to remove them.",
                    fg="bright_white",
                ))
            print(
                style(
                    f"[*] Do you want to remove {old_tools_dir}/*, {old_searchsploit_rc}, and {old_tools_dict}?",
                    fg="bright_white",
                ))

            answer = cmd2.Cmd().select(["Yes", "No"])
            print(style(f"[+] You chose {answer}", fg="bright_green"))

            if answer == "Yes":
                shutil.rmtree(old_tools_dir)
                print(style(f"[+] {old_tools_dir} removed", fg="bright_green"))

                if old_tools_dict.exists():
                    old_tools_dict.unlink()
                    print(
                        style(f"[+] {old_tools_dict} removed",
                              fg="bright_green"))

                if old_searchsploit_rc.exists():
                    old_searchsploit_rc.unlink()
                    print(
                        style(f"[+] {old_searchsploit_rc} removed",
                              fg="bright_green"))

                print(
                    style(
                        "[=] Please run the install all command to complete setup",
                        fg="bright_blue"))

        rs = ReconShell(persistent_history_file="~/.reconshell_history",
                        persistent_history_length=10000)
        sys.exit(rs.cmdloop())