From 5fcd940067aa149d87a0a8975dde17b18814171c Mon Sep 17 00:00:00 2001 From: NathanVong <144301410+NathanVong@users.noreply.github.com> Date: Sat, 9 May 2026 16:27:55 +0800 Subject: [PATCH 1/4] Add -p option for output path and code cleanup feat: add -p option to specify output path; refactor code for permission checks --- cupp.py | 81 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/cupp.py b/cupp.py index eed34ad..fefc211 100755 --- a/cupp.py +++ b/cupp.py @@ -174,7 +174,7 @@ def version(): print(" Take a look ./README.md file for more info about the program\r\n") -def improve_dictionary(file_to_open): +def improve_dictionary(file_to_open, directory): """Implementation of the -w option. Improve a dictionary by interactively questioning the user.""" @@ -290,13 +290,13 @@ def improve_dictionary(file_to_open): for x in unique_list if len(x) > CONFIG["global"]["wcfrom"] and len(x) < CONFIG["global"]["wcto"] ] - - print_to_file(file_to_open + ".cupp.txt", unique_list_finished) + base_name = os.path.basename(file_to_open) + print_to_file(os.path.join(directory, base_name + ".cupp.txt"), unique_list_finished) fajl.close() -def interactive(): +def interactive(directory): """Implementation of the -i switch. Interactively question the user and create a password dictionary file based on the answer.""" @@ -365,10 +365,10 @@ def interactive(): ).lower() profile["leetmode"] = input("> Leet mode? (i.e. leet = 1337) Y/[N]: ").lower() - generate_wordlist_from_profile(profile) # generate the wordlist + generate_wordlist_from_profile(profile, directory) # generate the wordlist -def generate_wordlist_from_profile(profile): +def generate_wordlist_from_profile(profile, directory): """ Generates a wordlist from a given profile """ chars = CONFIG["global"]["chars"] @@ -700,7 +700,7 @@ def generate_wordlist_from_profile(profile): if len(x) < CONFIG["global"]["wcto"] and len(x) > CONFIG["global"]["wcfrom"] ] - print_to_file(profile["name"] + ".txt", unique_list_finished) + print_to_file(os.path.join(directory, profile["name"] + ".txt"), unique_list_finished) def download_http(url, targetfile): @@ -712,7 +712,7 @@ def download_http(url, targetfile): localFile.close() -def alectodb_download(): +def alectodb_download(directory): """Download csv from alectodb and save into local file as a list of usernames and passwords""" @@ -739,20 +739,21 @@ def alectodb_download(): gpa = list(set(passwords)) gus.sort() gpa.sort() - + usernames_path = os.path.join(directory, "alectodb-usernames.txt") + passwords_path = os.path.join(directory, "alectodb-passwords.txt") print( - "\r\n[+] Exporting to alectodb-usernames.txt and alectodb-passwords.txt\r\n[+] Done." + f"\r\n[+] Exporting to {usernames_path} and {passwords_path}\r\n[+] Done." ) - f = open("alectodb-usernames.txt", "w") + f = open(usernames_path, "w") f.write(os.linesep.join(gus)) f.close() - f = open("alectodb-passwords.txt", "w") + f = open(passwords_path, "w") f.write(os.linesep.join(gpa)) f.close() -def download_wordlist(): +def download_wordlist(directory): """Implementation of -l switch. Download wordlists from http repository as defined in the configuration file.""" @@ -781,25 +782,22 @@ def download_wordlist(): ) filedown = input("> Enter number: ") - filedown.isdigit() while filedown.isdigit() == 0: print("\r\n[-] Wrong choice. ") filedown = input("> Enter number: ") filedown = str(filedown) - while int(filedown) > 38 or int(filedown) < 0: + while int(filedown) > 38 or int(filedown) < 1: print("\r\n[-] Wrong choice. ") filedown = input("> Enter number: ") filedown = str(filedown) - download_wordlist_http(filedown) + download_wordlist_http(filedown, directory) return filedown -def download_wordlist_http(filedown): +def download_wordlist_http(filedown, directory): """ do the HTTP download of a wordlist """ - mkdir_if_not_exists("dictionaries") - # List of files to download: arguments = { 1: ( @@ -995,13 +993,13 @@ def download_wordlist_http(filedown): if intfiledown in arguments: - dire = "dictionaries/" + arguments[intfiledown][0] + "/" - mkdir_if_not_exists(dire) + dire = os.path.join(directory, "dictionaries", arguments[intfiledown][0]) + mkdir(dire) files_to_download = arguments[intfiledown][1] for fi in files_to_download: url = CONFIG["global"]["dicturl"] + arguments[intfiledown][0] + "/" + fi - tgt = dire + fi + tgt = os.path.join(dire, fi) download_http(url, tgt) print("[+] files saved to " + dire) @@ -1010,10 +1008,17 @@ def download_wordlist_http(filedown): print("[-] leaving.") -# create the directory if it doesn't exist -def mkdir_if_not_exists(dire): - if not os.path.isdir(dire): - os.mkdir(dire) +# create the directory +def mkdir(dire): + try: + if os.path.exists(dire): + if not os.access(dire, os.W_OK): + raise PermissionError(f"No write permission on existing directory {dire}") + else: + os.makedirs(dire) + except PermissionError as e: + print(f"[-] Error: {e}") + sys.exit(1) # the main function @@ -1030,16 +1035,22 @@ def main(): if args.version: version() - elif args.interactive: - interactive() + return + elif not any([args.interactive, args.download_wordlist, args.alecto, args.improve]): + parser.print_help() + return + + user_dir = os.path.abspath(os.path.expanduser(args.path)) + mkdir(user_dir) + + if args.interactive: + interactive(user_dir) elif args.download_wordlist: - download_wordlist() + download_wordlist(user_dir) elif args.alecto: - alectodb_download() + alectodb_download(user_dir) elif args.improve: - improve_dictionary(args.improve) - else: - parser.print_help() + improve_dictionary(args.improve, user_dir) # Separate into a function for testing purposes @@ -1082,7 +1093,9 @@ def get_parser(): parser.add_argument( "-q", "--quiet", action="store_true", help="Quiet mode (don't print banner)" ) - + parser.add_argument( + "-p", "--path", action = "store", default = "./", help = "Set the directory for the generated output file (default: current directory)", metavar = "DIR" + ) return parser From ae0350540d49c2be994f6bfe361adbb6fab47634 Mon Sep 17 00:00:00 2001 From: NathanVong <144301410+NathanVong@users.noreply.github.com> Date: Sat, 9 May 2026 19:09:45 +0800 Subject: [PATCH 2/4] Update README with enhancements and usage instructions Added details about enhancements and new features in the README. --- README.md | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 19354b5..b6234b8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ +# Cupp (Enhanced Fork) + +This is an enhanced version of the original [Mebus/cupp](https://github.com/Mebus/cupp) repository. + +Since the original repository is currently not accepting external contributions, I have published this fork to share improvements and bug fixes that I have implemented for my own use. + +## Key Improvements +- **New Feature:** Added the `-p` flag to specify a custom output file path. +- **Code Cleanup:** Removed redundant code to improve maintainability and readability. +- **Stability:** Added write permission checks to prevent runtime errors when saving files. + +## Usage +You can now specify an output path using the `-p` argument (using interactive mode as an example): + +```bash +python3 cupp.py -i -p /path/to/your/output/ +``` + # CUPP - Common User Passwords Profiler [![Build Status](https://travis-ci.org/Mebus/cupp.svg?branch=master)](https://travis-ci.org/Mebus/cupp) @@ -34,23 +52,16 @@ Quick start $ python3 cupp.py -h -## Options - - Usage: cupp.py [OPTIONS] - - -h this menu - - -i Interactive questions for user password profiling - - -w Use this option to profile existing dictionary, - or WyD.pl output to make some pwnsauce :) - - -l Download huge wordlists from repository - - -a Parse default usernames and passwords directly from Alecto DB. - Project Alecto uses purified databases of Phenoelit and CIRT which where merged and enhanced. +### Options +Usage: python3 cupp.py [OPTIONS] - -v Version of the program +- **-h** : this menu +- **-i** : Interactive questions for user password profiling +- **-w** : Use this option to profile existing dictionary, or WyD.pl output to make some pwnsauce :) +- **-l** : Download huge wordlists from repository +- **-a** : Parse default usernames and passwords directly from Alecto DB. Project Alecto uses purified databases of Phenoelit and CIRT which where merged and enhanced. +- **-v** : Version of the program +- **-p** : Set the directory for the generated output file (default: current directory) **(New in this fork)** From e0a74f5dd63b08b08f2e3a0497272fa4d8ff5b33 Mon Sep 17 00:00:00 2001 From: NathanVong <144301410+NathanVong@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:31:13 +1000 Subject: [PATCH 3/4] docs: clean up README for pull request docs: clean up README for pull request --- README.md | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/README.md b/README.md index b6234b8..a85726b 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,3 @@ -# Cupp (Enhanced Fork) - -This is an enhanced version of the original [Mebus/cupp](https://github.com/Mebus/cupp) repository. - -Since the original repository is currently not accepting external contributions, I have published this fork to share improvements and bug fixes that I have implemented for my own use. - -## Key Improvements -- **New Feature:** Added the `-p` flag to specify a custom output file path. -- **Code Cleanup:** Removed redundant code to improve maintainability and readability. -- **Stability:** Added write permission checks to prevent runtime errors when saving files. - -## Usage -You can now specify an output path using the `-p` argument (using interactive mode as an example): - -```bash -python3 cupp.py -i -p /path/to/your/output/ -``` - # CUPP - Common User Passwords Profiler [![Build Status](https://travis-ci.org/Mebus/cupp.svg?branch=master)](https://travis-ci.org/Mebus/cupp) @@ -61,7 +43,7 @@ Usage: python3 cupp.py [OPTIONS] - **-l** : Download huge wordlists from repository - **-a** : Parse default usernames and passwords directly from Alecto DB. Project Alecto uses purified databases of Phenoelit and CIRT which where merged and enhanced. - **-v** : Version of the program -- **-p** : Set the directory for the generated output file (default: current directory) **(New in this fork)** +- **-p** : Set the directory for the generated output file (default: current directory) From dafa3730bd9d62f5eb3b96a1b92f8334a197e242 Mon Sep 17 00:00:00 2001 From: NathanVong <144301410+NathanVong@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:32:31 +1000 Subject: [PATCH 4/4] docs: clean up README for pull request docs: clean up README for pull request --- README.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a85726b..9f29671 100644 --- a/README.md +++ b/README.md @@ -34,16 +34,24 @@ Quick start $ python3 cupp.py -h -### Options -Usage: python3 cupp.py [OPTIONS] - -- **-h** : this menu -- **-i** : Interactive questions for user password profiling -- **-w** : Use this option to profile existing dictionary, or WyD.pl output to make some pwnsauce :) -- **-l** : Download huge wordlists from repository -- **-a** : Parse default usernames and passwords directly from Alecto DB. Project Alecto uses purified databases of Phenoelit and CIRT which where merged and enhanced. -- **-v** : Version of the program -- **-p** : Set the directory for the generated output file (default: current directory) +## Options + + Usage: cupp.py [OPTIONS] + + -h this menu + + -i Interactive questions for user password profiling + + -w Use this option to profile existing dictionary, + or WyD.pl output to make some pwnsauce :) + + -l Download huge wordlists from repository + + -a Parse default usernames and passwords directly from Alecto DB. + Project Alecto uses purified databases of Phenoelit and CIRT which where merged and enhanced. + + -v Version of the program + -p : Set the directory for the generated output file (default: current directory)