From e71e6e04311d90f8d78e3a41dbd072f740cbdbd7 Mon Sep 17 00:00:00 2001
From: Marco Lancini <info@marcolancini.it>
Date: Fri, 18 May 2018 17:27:34 +0100
Subject: [PATCH] Add code

---
 README.md                     |   2 +-
 goscan/Makefile               |  41 +++
 goscan/core/cli/completer.go  | 198 ++++++++++++++
 goscan/core/cli/executor.go   | 143 +++++++++++
 goscan/core/model/model.go    | 106 ++++++++
 goscan/core/scan/enumerate.go | 470 ++++++++++++++++++++++++++++++++++
 goscan/core/scan/enumscan.go  |  38 +++
 goscan/core/scan/nmap.go      | 126 +++++++++
 goscan/core/scan/portscan.go  | 109 ++++++++
 goscan/core/scan/sweep.go     | 100 ++++++++
 goscan/core/utils/logger.go   |  42 +++
 goscan/core/utils/net.go      |  30 +++
 goscan/core/utils/utils.go    | 167 ++++++++++++
 goscan/main.go                |  68 +++++
 14 files changed, 1639 insertions(+), 1 deletion(-)
 create mode 100644 goscan/Makefile
 create mode 100644 goscan/core/cli/completer.go
 create mode 100644 goscan/core/cli/executor.go
 create mode 100644 goscan/core/model/model.go
 create mode 100644 goscan/core/scan/enumerate.go
 create mode 100644 goscan/core/scan/enumscan.go
 create mode 100644 goscan/core/scan/nmap.go
 create mode 100644 goscan/core/scan/portscan.go
 create mode 100644 goscan/core/scan/sweep.go
 create mode 100644 goscan/core/utils/logger.go
 create mode 100644 goscan/core/utils/net.go
 create mode 100644 goscan/core/utils/utils.go
 create mode 100644 goscan/main.go

diff --git a/README.md b/README.md
index 6ab2697..51e30d0 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
 
 It can be used to perform host discovery, port scanning, and service enumeration in situations where being stealthy is not a priority, and time is limited (think at CTFs, OSCP, exams, etc.).
 
-<a href="https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ" target="_blank"><img src="https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ.png"/></a>
+[![asciicast](https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ.png)](https://asciinema.org/a/4ebtOAiDKmM1X89yCIpFQjqSQ)
 
 
 
diff --git a/goscan/Makefile b/goscan/Makefile
new file mode 100644
index 0000000..9586355
--- /dev/null
+++ b/goscan/Makefile
@@ -0,0 +1,41 @@
+NAME := goscan
+VERSION := 1.0
+AUTHOR := Marco Lancini [@LanciniMarco]
+LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.author=$(AUTHOR)'
+
+.DEFAULT_GOAL := help
+
+.PHONY: setup
+setup:  ## Setup for required tools.
+	go get github.com/golang/lint/golint
+	go get golang.org/x/tools/cmd/goimports
+	go get -u github.com/golang/dep/cmd/dep
+	dep ensure
+
+.PHONY: fmt
+fmt: ## Formatting source codes.
+	go fmt ./...
+
+.PHONY: lint
+lint: ## Run golint and go vet.
+	@golint ./core/...
+	@go vet ./core/...
+
+.PHONY: build
+build: main.go  ## Build a binary.
+	go build -ldflags "$(LDFLAGS)"
+
+.PHONY: cross
+cross: main.go  ## Build binaries for cross platform.
+	mkdir -p pkg
+	@for os in "darwin" "linux"; do \
+		for arch in "amd64" "386"; do \
+			GOOS=$${os} GOARCH=$${arch} make build; \
+			zip pkg/goscan_$(VERSION)_$${os}_$${arch}.zip goscan; \
+		done; \
+	done
+
+.PHONY: help
+help: ## Show help text
+	@echo "Commands:"
+	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "    \033[36m%-20s\033[0m %s\n", $$1, $$2}'
diff --git a/goscan/core/cli/completer.go b/goscan/core/cli/completer.go
new file mode 100644
index 0000000..3dfe3a0
--- /dev/null
+++ b/goscan/core/cli/completer.go
@@ -0,0 +1,198 @@
+package cli
+
+import (
+	"fmt"
+	"github.com/c-bata/go-prompt"
+	"goscan/core/utils"
+	"io/ioutil"
+	"path/filepath"
+	"strings"
+)
+
+func excludeOptions(args []string) []string {
+	ret := make([]string, 0, len(args))
+	for i := range args {
+		if !strings.HasPrefix(args[i], "-") {
+			ret = append(ret, args[i])
+		}
+	}
+	return ret
+}
+
+func Completer(d prompt.Document) []prompt.Suggest {
+	if d.TextBeforeCursor() == "" {
+		return []prompt.Suggest{}
+	}
+	args := strings.Split(d.TextBeforeCursor(), " ")
+
+	// If PIPE is in text before the cursor, returns empty suggestions.
+	for i := range args {
+		if args[i] == "|" {
+			return []prompt.Suggest{}
+		}
+	}
+	return argumentsCompleter(d, excludeOptions(args))
+}
+
+var commands = []prompt.Suggest{
+	{Text: "set_target", Description: "Set target CIDR."},
+	{Text: "set_output_folder", Description: "Set the output folder."},
+	{Text: "show", Description: "Show results."},
+	{Text: "sweep", Description: "Perform an ARP/ping sweep."},
+	{Text: "portscan", Description: "Perform a port scan."},
+	{Text: "enumerate", Description: "Perform enumeration of detected services."},
+	{Text: "help", Description: "Show help"},
+	{Text: "exit", Description: "Exit this program"},
+}
+
+func argumentsCompleter(d prompt.Document, args []string) []prompt.Suggest {
+	if len(args) <= 1 {
+		return prompt.FilterHasPrefix(commands, args[0], true)
+	}
+
+	first := args[0]
+	switch first {
+	case "set_target":
+		if len(args) == 2 {
+			return prompt.FilterContains(getTargetSuggestions(), args[1], true)
+		}
+	case "set_output_folder":
+		return fileCompleter(d)
+	case "show":
+		second := args[1]
+		if len(args) == 2 {
+			subcommands := []prompt.Suggest{
+				{Text: "hosts", Description: "Show live hosts"},
+			}
+			return prompt.FilterHasPrefix(subcommands, second, true)
+		}
+	case "sweep":
+		second := args[1]
+		if len(args) == 2 {
+			subcommands := []prompt.Suggest{
+				{Text: "ALL", Description: "Perform ARP scan and PING sweep"},
+				{Text: "ARP", Description: "Perform ARP scan"},
+				{Text: "PING", Description: "Perform PING sweep"},
+			}
+			return prompt.FilterHasPrefix(subcommands, second, true)
+		}
+		if len(args) == 3 {
+			return prompt.FilterContains(getTargetSuggestions(), args[2], true)
+		}
+	case "portscan":
+		second := args[1]
+		if len(args) == 2 {
+			subcommands := []prompt.Suggest{
+				{Text: "TCP-FULL", Description: "Perform FULL TCP scan"},
+				{Text: "TCP-STANDARD", Description: "Perform TCP scan (top 200)"},
+				{Text: "TCP-VULN-SCAN", Description: "Perform TCP VULN scan (vulscan.nse)"},
+				{Text: "UDP-STANDARD", Description: "Perform UDP scan (common ports)"},
+			}
+			return prompt.FilterHasPrefix(subcommands, second, true)
+		}
+		if len(args) == 3 {
+			return prompt.FilterContains(getHostSuggestions(), args[2], true)
+		}
+	case "enumerate":
+		if len(args) == 2 {
+			second := args[1]
+			subcommands := []prompt.Suggest{
+				{Text: "ALL", Description: "Automatically identify open services and enumerate them"},
+				{Text: "DNS", Description: "Enumerate DNS"},
+				{Text: "FINGER", Description: "Enumerate FINGER"},
+				{Text: "FTP", Description: "Enumerate FTP"},
+				{Text: "HTTP", Description: "Enumerate HTTP"},
+				{Text: "RDP", Description: "Enumerate RDP"},
+				{Text: "SMB", Description: "Enumerate SMB"},
+				{Text: "SMTP", Description: "Enumerate SMTP"},
+				{Text: "SNMP", Description: "Enumerate SNMP"},
+				{Text: "SQL", Description: "Enumerate SQL"},
+				{Text: "SSH", Description: "Enumerate SSH"},
+			}
+			return prompt.FilterHasPrefix(subcommands, second, true)
+		}
+		if len(args) == 3 {
+			third := args[2]
+			subcommands := []prompt.Suggest{
+				{Text: "POLITE", Description: "Avoid bruteforcing"},
+				{Text: "BRUTEFORCE", Description: "Include bruteforce scripts"},
+			}
+			return prompt.FilterHasPrefix(subcommands, third, true)
+		}
+		if len(args) == 4 {
+			return prompt.FilterContains(getHostSuggestions(), args[3], true)
+		}
+	default:
+		return []prompt.Suggest{}
+	}
+
+	return []prompt.Suggest{}
+}
+
+func getHostSuggestions() []prompt.Suggest {
+	s := []prompt.Suggest{}
+	s = append(s, prompt.Suggest{
+		Text:        "ALL",
+		Description: "Scan all live hosts",
+	})
+
+	for _, h := range utils.Config.Hosts {
+		s = append(s, prompt.Suggest{
+			Text:        h.Address,
+			Description: fmt.Sprintf("Scan only host: %s", h.Address),
+		})
+	}
+
+	return s
+}
+
+func getTargetSuggestions() []prompt.Suggest {
+	// Default suggestions
+	var target string = utils.Const_example_target_cidr
+	var desc string = utils.Const_example_target_desc
+
+	// Detect if a target has already been selected
+	if utils.Config.Target != "" {
+		target = fmt.Sprintf("%s", utils.Config.Target)
+		desc = "Currently selected target"
+	}
+
+	// Build suggestion
+	s := make([]prompt.Suggest, 1, 5)
+	s[0] = prompt.Suggest{
+		Text:        target,
+		Description: desc,
+	}
+
+	// Parse address from network interface
+	localInterfaces := utils.ParseLocalIP()
+	for eth, ip := range localInterfaces {
+		s = append(s, prompt.Suggest{
+			Text:        utils.ParseCIDR(ip),
+			Description: fmt.Sprintf("Subnet from interface: %s", eth),
+		})
+
+	}
+
+	return s
+}
+
+func fileCompleter(d prompt.Document) []prompt.Suggest {
+	path := d.GetWordBeforeCursor()
+	if strings.HasPrefix(path, "./") {
+		path = path[2:]
+	}
+	dir := filepath.Dir(path)
+	files, err := ioutil.ReadDir(dir)
+	if err != nil {
+		return []prompt.Suggest{}
+	}
+	suggests := make([]prompt.Suggest, 0, len(files))
+	for _, f := range files {
+		if !f.IsDir() {
+			continue
+		}
+		suggests = append(suggests, prompt.Suggest{Text: filepath.Join(dir, f.Name())})
+	}
+	return prompt.FilterHasPrefix(suggests, path, false)
+}
diff --git a/goscan/core/cli/executor.go b/goscan/core/cli/executor.go
new file mode 100644
index 0000000..cc8068f
--- /dev/null
+++ b/goscan/core/cli/executor.go
@@ -0,0 +1,143 @@
+package cli
+
+import (
+	"fmt"
+	"github.com/olekukonko/tablewriter"
+	"goscan/core/scan"
+	"goscan/core/utils"
+	"os"
+)
+
+func Executor(s string) {
+	// Parse cmd
+	cmd, args := utils.ParseCmd(s)
+
+	// Execute commands
+	switch cmd {
+
+	case "set_target":
+		cmdSetTarget(args)
+	case "set_output_folder":
+		cmdSetOutputFolder(args)
+	case "show":
+		cmdShow(args)
+	case "sweep":
+		cmdSweep(args)
+	case "portscan":
+		cmdPortscan(args)
+	case "enumerate":
+		cmdEnumerate(args)
+	case "help":
+		cmdHelp()
+	case "exit", "quit":
+		os.Exit(0)
+		return
+
+	case "":
+	default:
+		return
+	}
+
+	// Start checking for running scans
+	go scan.ReportStatusNmap()
+	go scan.ReportStatusEnum()
+}
+
+func cmdHelp() {
+	utils.Config.Log.LogInfo("GoScan automates the scanning and enumeration steps of a penetration test")
+	utils.Config.Log.LogInfo("Available commands:")
+
+	data := [][]string{
+		[]string{"Set output folder", "set_output_folder <PATH>"},
+		[]string{"Ping Sweep", "sweep <TYPE> <TARGET>"},
+		[]string{"Port Scan", "portscan <TYPE> <TARGET>"},
+		[]string{"Service Enumeration", "enumerate <TYPE> <POLITE/AGGRESSIVE> <TARGET>"},
+		[]string{"Show live hosts", "show hosts"},
+	}
+	table := tablewriter.NewWriter(os.Stdout)
+	table.SetHeader([]string{"Command", "Syntax"})
+	table.SetAlignment(3)
+	table.SetAutoWrapText(false)
+	table.AppendBulk(data)
+	table.Render()
+}
+
+func cmdShow(args []string) {
+	what, _ := utils.ParseNextArg(args)
+	switch what {
+	case "hosts":
+		utils.ShowHosts()
+	}
+}
+
+func cmdSetTarget(args []string) bool {
+	if len(args) != 1 {
+		utils.Config.Log.LogError("Invalid command provided")
+		return false
+	}
+	ip, _ := utils.ParseNextArg(args)
+	cidr := utils.ParseCIDR(ip)
+	if cidr == "" {
+		utils.Config.Log.LogError("Invalid CIDR provided")
+		return false
+	}
+	utils.Config.Log.LogInfo(fmt.Sprintf("Selected target: %s", cidr))
+	utils.Config.Target = cidr
+
+	return true
+}
+
+func cmdSetOutputFolder(args []string) {
+	if len(args) != 1 {
+		utils.Config.Log.LogError("Invalid command provided")
+		return
+	}
+	folder, _ := utils.ParseNextArg(args)
+	utils.Config.Outfolder = folder
+	utils.EnsureDir(utils.Config.Outfolder)
+}
+
+func cmdSweep(args []string) {
+	if len(args) != 2 {
+		utils.Config.Log.LogError("Invalid command provided")
+		return
+	}
+	// Get type of scan
+	kind, args := utils.ParseNextArg(args)
+	// Get target and update global config
+	target, _ := utils.ParseNextArg(args)
+	check := cmdSetTarget(args)
+	if check == false {
+		return
+	}
+	// Perform ping sweep
+	scan.ScanSweep(target, kind)
+}
+
+func cmdPortscan(args []string) {
+	if len(args) != 2 {
+		utils.Config.Log.LogError("Invalid command provided")
+		return
+	}
+	// Get type of scan
+	kind, args := utils.ParseNextArg(args)
+	// Get target host
+	target, _ := utils.ParseNextArg(args)
+	// Perform port scan
+	scan.ScanPort(target, kind)
+}
+
+func cmdEnumerate(args []string) {
+	if len(args) != 3 {
+		utils.Config.Log.LogError("Invalid command provided")
+		return
+	}
+	// Get type of scan
+	kind, args := utils.ParseNextArg(args)
+	// Get politeness
+	polite, args := utils.ParseNextArg(args)
+	// Get target host
+	target, _ := utils.ParseNextArg(args)
+	// Perform port scan
+	scan.ScanEnumerate(target, polite, kind)
+}
diff --git a/goscan/core/model/model.go b/goscan/core/model/model.go
new file mode 100644
index 0000000..e6304cc
--- /dev/null
+++ b/goscan/core/model/model.go
@@ -0,0 +1,106 @@
+package model
+
+import (
+	"fmt"
+)
+
+// ---------------------------------------------------------------------------------------
+// SCAN STRUCTURE
+// ---------------------------------------------------------------------------------------
+type Scan struct {
+	Name      string
+	Target    string
+	Status    int
+	Outfolder string
+	Outfile   string
+	Cmd       string
+	Result    []byte
+}
+
+func (s *Scan) String() string {
+	return fmt.Sprintf("Target: %s [%d]", s.Target, s.Status)
+}
+
+// ---------------------------------------------------------------------------------------
+// ENUMERATE STRUCTURE
+// ---------------------------------------------------------------------------------------
+type Enumeration struct {
+	Target    *Host
+	Outfolder string
+	Kind      string
+	Status    int
+	Result    []byte
+	Polite    string
+}
+
+func (e *Enumeration) String() string {
+	return fmt.Sprintf("Enumeration [%s]: %s [%d]", e.Kind, e.Target.Address, e.Status)
+}
+
+// ---------------------------------------------------------------------------------------
+// SERVICE
+// ---------------------------------------------------------------------------------------
+type Service struct {
+	Name    string
+	Version string
+	Product string
+	OsType  string
+}
+
+func (s *Service) String() string {
+	return fmt.Sprintf("%s%s - %s", s.Name, s.Version, s.Product)
+}
+
+// ---------------------------------------------------------------------------------------
+// PORT
+// ---------------------------------------------------------------------------------------
+type Port struct {
+	Number   int
+	Protocol string
+	Status   string
+	Service  Service
+}
+
+func (p *Port) String() string {
+	return fmt.Sprintf("%5d/%s %-8s: %s", p.Number, p.Protocol, p.Status, p.Service)
+}
+
+// Returns true if 2 Ports are the same
+func (p *Port) Equal(other Port) bool {
+	return (p.Number == other.Number) && (p.Protocol == other.Protocol)
+}
+
+// ---------------------------------------------------------------------------------------
+// HOST
+// ---------------------------------------------------------------------------------------
+type Host struct {
+	Address string
+	Status  string
+	OS      string
+	Ports   []Port
+}
+
+func (h *Host) String() string {
+	out := fmt.Sprintf("%s - %s", h.Status, h.Address)
+	if h.OS != "" {
+		out = fmt.Sprintf("%s [%s] ", out, h.OS)
+	}
+	for i := 0; i < len(h.Ports); i++ {
+		out = fmt.Sprintf("%s [%s]", out, h.Ports[i].String())
+	}
+	return out
+}
+
+// Add port to the list only if it's new
+func (h *Host) AddPort(newPort Port) {
+
+	existing := false
+	for _, p := range h.Ports {
+		if p.Equal(newPort) {
+			existing = true
+		}
+	}
+	if existing == false {
+		h.Ports = append(h.Ports, newPort)
+	}
+}
diff --git a/goscan/core/scan/enumerate.go b/goscan/core/scan/enumerate.go
new file mode 100644
index 0000000..2c6c9dc
--- /dev/null
+++ b/goscan/core/scan/enumerate.go
@@ -0,0 +1,470 @@
+package scan
+
+import (
+	"fmt"
+	"goscan/core/model"
+	"goscan/core/utils"
+	"os/exec"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+// ---------------------------------------------------------------------------------------
+// ENUMERATION
+// ---------------------------------------------------------------------------------------
+type EnumScan model.Enumeration
+
+func NewEnumScan(target *model.Host, kind, polite string) *EnumScan {
+	// Create a Scan
+	s := &EnumScan{
+		Target: target,
+		Kind:   kind,
+		Polite: polite,
+		Status: not_started,
+	}
+	return s
+}
+
+func (s *EnumScan) preScan() {
+	s.Status = in_progress
+}
+func (s *EnumScan) postScan() {
+	s.Status = finished
+}
+
+func (s *EnumScan) makeOutputPath(folder, file string) string {
+	resFolder := filepath.Join(utils.Config.Outfolder, utils.CleanPath(s.Target.Address), folder)
+	resFile := filepath.Join(resFolder, file)
+	utils.EnsureDir(resFolder)
+	return resFile
+}
+
+func (s *EnumScan) runCmd(cmd string) ([]byte, error) {
+	utils.Config.Log.LogDebug(fmt.Sprintf("Running: %s", cmd))
+	res, err := exec.Command("sh", "-c", cmd).Output()
+	if err != nil {
+		utils.Config.Log.LogError(fmt.Sprintf("Failed Enumeration: %s", err))
+		s.Status = failed
+	}
+	return res, err
+}
+
+func (s *EnumScan) Run() {
+	// Pre-scan checks
+	s.preScan()
+
+	// Dispatch scan
+	switch s.Kind {
+	case "DNS":
+		s.EnumDNS()
+	case "FINGER":
+		s.EnumFINGER()
+	case "FTP":
+		s.EnumFTP()
+	case "HTTP":
+		s.EnumHTTP()
+	case "RDP":
+		s.EnumRDP()
+	case "SMB":
+		s.EnumSMB()
+	case "SMTP":
+		s.EnumSMTP()
+	case "SNMP":
+		s.EnumSNMP()
+	case "SQL":
+		s.EnumSQL()
+	case "SSH":
+		s.EnumSSH()
+	case "ALL":
+		s.EnumDNS()
+		s.EnumFINGER()
+		s.EnumFTP()
+		s.EnumHTTP()
+		s.EnumRDP()
+		s.EnumSMB()
+		s.EnumSMTP()
+		s.EnumSNMP()
+		s.EnumSQL()
+		s.EnumSSH()
+	}
+
+	// Post-scan checks
+	s.postScan()
+
+}
+
+// ---------------------------------------------------------------------------------------
+// SCANNERS
+// ---------------------------------------------------------------------------------------
+func (s *EnumScan) EnumDNS() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "dns") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := "dns_nmap"
+				nmapArgs := fmt.Sprintf("-sV -Pn -sU -p53,%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "FTP", name, nmapArgs)
+				nmap.RunNmap()
+
+				// POLITE
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumFINGER() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			if s.Target.Ports[i].Number == 79 {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, "finger"))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := fmt.Sprintf("finger_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=finger -p%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "FINGER", name, nmapArgs)
+				nmap.RunNmap()
+
+				// -----------------------------------------------------------------------
+				// FINGER-USER-ENUM
+				// -----------------------------------------------------------------------
+				output := s.makeOutputPath("FINGER", "finger_user-enum")
+				cmd := fmt.Sprintf("finger-user-enum.pl -U %s -t %s > %s", utils.WORDLIST_FINGER_USER, s.Target.Address, output)
+				s.runCmd(cmd)
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumFTP() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "ftp") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := fmt.Sprintf("ftp_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=ftp-anon,ftp-bounce,ftp-libopie,ftp-proftpd-backdoor,ftp-vsftpd-backdoor,ftp-vuln-cve2010-4221 -p%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "FTP", name, nmapArgs)
+				nmap.RunNmap()
+
+				// -----------------------------------------------------------------------
+				// FTP-USER-ENUM
+				// -----------------------------------------------------------------------
+				output := s.makeOutputPath("FTP", "ftp_user-enum")
+				cmd := fmt.Sprintf("ftp-user-enum.pl -U %s -t %s > %s", utils.WORDLIST_FTP_USER, s.Target.Address, output)
+				s.runCmd(cmd)
+
+				// -----------------------------------------------------------------------
+				// HYDRA
+				// -----------------------------------------------------------------------
+				// If not polite scan
+				if s.Polite != "POLITE" {
+					// Build command
+					output := s.makeOutputPath("FTP", "ftp_hydra")
+					cmd := fmt.Sprintf("hydra -L %s -P %s -f -o %s -u %s -s %d ftp",
+						utils.WORDLIST_HYDRA_FTP_USER, utils.WORDLIST_HYDRA_FTP_PWD,
+						output,
+						s.Target.Address, s.Target.Ports[i].Number,
+					)
+					// Run command
+					s.runCmd(cmd)
+				}
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumHTTP() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "http") || strings.Contains(strings.ToLower(service), "https") || strings.Contains(strings.ToLower(service), "ssl/http") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := fmt.Sprintf("http_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=http-vhosts,http-userdir-enum,http-apache-negotiation,http-backup-finder,http-config-backup,http-default-accounts,http-methods,http-method-tamper,http-passwd,http-sitemap-generator,http-auth-finder,http-auth,http-fileupload-exploiter,http-put,http-sql-injection,http-stored-xss,http-xssed,http-php-version,http-unsafe-output-escaping,http-phpmyadmin-dir-traversal,http-ntlm-info,http-phpself-xss,http-open-redirect,http-iis-webdav-vuln,http-form-fuzzer,http-vuln-cve2009-3960,http-vuln-cve2010-0738,http-vuln-cve2010-2861,http-vuln-cve2011-3368,http-vuln-cve2012-1823,http-vuln-cve2013-0156,http-robots.txt,http-wordpress-brute,http-wordpress-enum --script-args http-put.url='/uploads/rootme.php',http-put.file='/root/www/php-reverse.php',basepath='/' -p%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "HTTP", name, nmapArgs)
+				nmap.RunNmap()
+
+				// -----------------------------------------------------------------------
+				// NIKTO
+				// -----------------------------------------------------------------------
+				// Build command
+				output := s.makeOutputPath("HTTP", fmt.Sprintf("http_nikto_%d", s.Target.Ports[i].Number))
+				cmd := fmt.Sprintf("nikto -host %s -p %d > %s", s.Target.Address, s.Target.Ports[i].Number, output)
+				// Run command
+				s.runCmd(cmd)
+
+				// -----------------------------------------------------------------------
+				// DIRB
+				// -----------------------------------------------------------------------
+				// Build command
+				output = s.makeOutputPath("HTTP", fmt.Sprintf("http_dirb_%d", s.Target.Ports[i].Number))
+				protocol := "https"
+				if strings.Contains(strings.ToLower(service), "http") {
+					protocol = "http"
+				}
+				cmd = fmt.Sprintf("dirb %s://%s:%d -o %s -S -r", protocol, s.Target.Address, s.Target.Ports[i].Number, output)
+				// Run command
+				s.runCmd(cmd)
+
+				// If not polite scan
+				if s.Polite != "POLITE" {
+					// -------------------------------------------------------------------
+					// SQLMAP
+					// -------------------------------------------------------------------
+					// Build command
+					output := s.makeOutputPath("HTTP", fmt.Sprintf("http_sqlmap_%d", s.Target.Ports[i].Number))
+					protocol := "https"
+					if strings.Contains(strings.ToLower(service), "http") {
+						protocol = "http"
+					}
+					cmd := fmt.Sprintf("sqlmap -u %s://%s:%d --crawl=1 > %s", protocol, s.Target.Address, s.Target.Ports[i].Number, output)
+					// Run command
+					s.runCmd(cmd)
+
+					// -------------------------------------------------------------------
+					// FIMAP
+					// -------------------------------------------------------------------
+					// Build command
+					output = s.makeOutputPath("HTTP", fmt.Sprintf("http_fimap_%d", s.Target.Ports[i].Number))
+					protocol = "https"
+					if strings.Contains(strings.ToLower(service), "http") {
+						protocol = "http"
+					}
+					cmd = fmt.Sprintf("fimap -u \"%s://%s:%d\" > %s", protocol, s.Target.Address, s.Target.Ports[i].Number, output)
+					// Run command
+					s.runCmd(cmd)
+				}
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumRDP() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "ms-wbt-server") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := fmt.Sprintf("rdp_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV --script=rdp-vuln-ms12-020 -p%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "RDP", name, nmapArgs)
+				nmap.RunNmap()
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumSMB() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "smb") || strings.Contains(strings.ToLower(service), "microsoft-ds") || strings.Contains(strings.ToLower(service), "netbios") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// POLITE
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumSMTP() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "smtp") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := fmt.Sprintf("smtp_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=smtp-enum-users,smtp-vuln* --script-args='smtp-vuln-cve2010-4344.exploit' -p25,465,587,%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "SMTP", name, nmapArgs)
+				nmap.RunNmap()
+
+				// -----------------------------------------------------------------------
+				// SMTP-USER-ENUM
+				// -----------------------------------------------------------------------
+				output := s.makeOutputPath("SMTP", "smtp_user-enum")
+				cmd := fmt.Sprintf("smtp-user-enum -M VRFY -U %s -t %s > %s", utils.WORDLIST_SMTP, s.Target.Address, output)
+				s.runCmd(cmd)
+
+				// POLITE
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumSNMP() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "snmp") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				name := fmt.Sprintf("snmp_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=snmp-netstat,snmp-processes,snmp-win32-services,snmp-win32-shares,snmp-win32-software,snmp-win32-users -p161,162,%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "SNMP", name, nmapArgs)
+				nmap.RunNmap()
+
+				// -----------------------------------------------------------------------
+				// SNMPCHECK
+				// -----------------------------------------------------------------------
+				output := s.makeOutputPath("SNMP", "snmp_snmpcheck")
+				cmd := fmt.Sprintf("snmpcheck %s > %s", s.Target.Address, output)
+				s.runCmd(cmd)
+
+				// POLITE
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumSSH() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "ssh") {
+				// Start Enumerating
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+				// If not polite scan
+				if s.Polite != "POLITE" {
+					// -------------------------------------------------------------------
+					// HYDRA
+					// -------------------------------------------------------------------
+					// Build command
+					output := s.makeOutputPath("SSH", "ssh_hydra")
+					cmd := fmt.Sprintf("hydra -L %s -P %s -f -o %s -u %s -s %d ssh",
+						utils.WORDLIST_HYDRA_SSH_USER, utils.WORDLIST_HYDRA_SSH_PWD,
+						output,
+						s.Target.Address, s.Target.Ports[i].Number,
+					)
+					// Run command
+					s.runCmd(cmd)
+				}
+			}
+		}
+	}
+}
+
+func (s *EnumScan) EnumSQL() {
+	for i := 0; i < len(s.Target.Ports); i++ {
+		// Enumerate only if port is open
+		if s.Target.Ports[i].Status == "open" {
+			// Dispatch the correct scanner
+			service := s.Target.Ports[i].Service.Name
+			if strings.Contains(strings.ToLower(service), "ms-sql") {
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+				name := fmt.Sprintf("sql_mssql_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=ms-sql-info,ms-sql-config,ms-sql-dump-hashes,ms-sql-brute,ms-sql-dac,ms-sql-empty-password,ms-sql-hasdbaccess,ms-sql-query,ms-sql-tables,ms-sql-xp-cmdshell --script-args mssql.instance-port=%d,mssql.username=sa,mssql.password=sa,ms-sql-query.query='SELECT * FROM master..syslogins' -p%d", s.Target.Ports[i].Number, s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "SQL", name, nmapArgs)
+				nmap.RunNmap()
+
+			} else if strings.Contains(strings.ToLower(service), "mysql") {
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+				name := fmt.Sprintf("sql_mysql_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=mysql-brute,mysql-databases,mysql-empty-password,mysql-enum,mysql-info,mysql-users,mysql-variables,mysql-vuln-cve2012-2122 -p%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "SQL", name, nmapArgs)
+				nmap.RunNmap()
+
+			} else if strings.Contains(strings.ToLower(service), "oracle") {
+				// -----------------------------------------------------------------------
+				// NMAP
+				// -----------------------------------------------------------------------
+				utils.Config.Log.LogInfo(fmt.Sprintf("Starting Enumeration: %s:%d (%s)", s.Target.Address, s.Target.Ports[i].Number, service))
+				name := fmt.Sprintf("sql_oracle_nmap_%d", s.Target.Ports[i].Number)
+				nmapArgs := fmt.Sprintf("-sV -Pn --script=oracle-brute,oracle-enum-users,oracle-sid-brute --script-args oracle-brute.sid=ORCL -p%d", s.Target.Ports[i].Number)
+				nmap := NewScan(name, s.Target.Address, "SQL", name, nmapArgs)
+				nmap.RunNmap()
+
+			}
+		}
+	}
+}
+
+// ---------------------------------------------------------------------------------------
+// SCAN MANAGEMENT
+// ---------------------------------------------------------------------------------------
+func ReportStatusEnum() {
+	ticker := time.Tick(notificationDelay)
+	for {
+		<-ticker
+
+		if len(EnumList) != 0 {
+			i := 0
+			for _, scan := range EnumList {
+				switch {
+				case scan.Status == null:
+					break
+				case scan.Status == failed:
+					utils.Config.Log.LogError(fmt.Sprintf("Enumeration failed on host: %s", scan.Target.Address))
+				case scan.Status == in_progress:
+					utils.Config.Log.LogInfo(fmt.Sprintf("Enumeration in progress on host (%s):\t%s", scan.Kind, scan.Target.Address))
+					// Update in place, remove finished scans
+					EnumList[i] = scan
+					i++
+				case scan.Status == finished:
+					utils.Config.Log.LogNotify(fmt.Sprintf("Enumeration finished on host (%s):\t%s", scan.Kind, scan.Target.Address))
+					utils.Config.Log.LogNotify(fmt.Sprintf("Output has been saved at (%s):\t%s", scan.Kind, utils.Config.Outfolder))
+				}
+			}
+			// Update in place, remove finished scans
+			EnumList = EnumList[:i]
+		}
+	}
+}
diff --git a/goscan/core/scan/enumscan.go b/goscan/core/scan/enumscan.go
new file mode 100644
index 0000000..16d876c
--- /dev/null
+++ b/goscan/core/scan/enumscan.go
@@ -0,0 +1,38 @@
+package scan
+
+import (
+	"goscan/core/model"
+	"goscan/core/utils"
+)
+
+// ---------------------------------------------------------------------------------------
+// CONSTANTS
+// ---------------------------------------------------------------------------------------
+var EnumList = []*EnumScan{}
+
+// ---------------------------------------------------------------------------------------
+// SCAN LAUNCHER
+// ---------------------------------------------------------------------------------------
+func ScanEnumerate(target string, polite string, kind string) {
+	for i := 0; i < len(utils.Config.Hosts); i++ {
+		// Scan only if:
+		//   - target is ALL
+		//   - or if host is the selected one
+		if target == "ALL" || target == utils.Config.Hosts[i].Address {
+			go workerEnum(&utils.Config.Hosts[i], kind, polite)
+		}
+	}
+}
+
+// ---------------------------------------------------------------------------------------
+// WORKER
+// ---------------------------------------------------------------------------------------
+func workerEnum(h *model.Host, kind string, polite string) {
+	// Instantiate new EnumScan
+	s := NewEnumScan(h, kind, polite)
+	EnumList = append(EnumList, s)
+
+	// Run the scan
+	s.Run()
+
+}
diff --git a/goscan/core/scan/nmap.go b/goscan/core/scan/nmap.go
new file mode 100644
index 0000000..5c8bbec
--- /dev/null
+++ b/goscan/core/scan/nmap.go
@@ -0,0 +1,126 @@
+package scan
+
+import (
+	"fmt"
+	go_nmap "github.com/lair-framework/go-nmap"
+	"goscan/core/model"
+	"goscan/core/utils"
+	"io/ioutil"
+	"os/exec"
+	"path/filepath"
+	"time"
+)
+
+// ---------------------------------------------------------------------------------------
+// CONSTANTS
+// ---------------------------------------------------------------------------------------
+const (
+	null = iota
+	not_started
+	in_progress
+	failed
+	done
+	finished
+)
+
+// ---------------------------------------------------------------------------------------
+// NMAP INTERACTION
+// ---------------------------------------------------------------------------------------
+type NmapScan model.Scan
+
+// Constructor for NmapScan
+func NewScan(name, target, folder, file, nmapArgs string) *NmapScan {
+	// Create a Scan
+	s := &NmapScan{
+		Name:   name,
+		Target: target,
+		Status: not_started,
+	}
+	// Construct output path and create if it doesn't exist
+	s.Outfolder = filepath.Join(utils.Config.Outfolder, utils.CleanPath(target), folder)
+	s.Outfile = filepath.Join(s.Outfolder, file)
+	utils.EnsureDir(s.Outfolder)
+	// Construct command
+	s.Cmd = s.constructCmd(nmapArgs)
+
+	return s
+}
+
+func (s *NmapScan) preScan() {
+	s.Status = in_progress
+}
+func (s *NmapScan) postScan() {
+	s.Status = finished
+}
+
+func (s *NmapScan) constructCmd(args string) string {
+	return fmt.Sprintf("nmap %s %s -oA %s", args, s.Target, s.Outfile)
+}
+
+// Run nmap scan
+func (s *NmapScan) RunNmap() {
+	// Pre-scan checks
+	s.preScan()
+
+	// Run nmap
+	utils.Config.Log.LogDebug(fmt.Sprintf("Nmap command: %s", s.Cmd))
+	res, err := exec.Command("sh", "-c", s.Cmd).Output()
+	if err != nil {
+		utils.Config.Log.LogError(fmt.Sprintf("Failed to nmap destination %s (%s): %s", s.Target, s.Name, err))
+		s.Status = failed
+	}
+	s.Result = res
+
+	// Post-scan checks
+	s.postScan()
+
+}
+
+// Parse nmap XML output file
+func (s *NmapScan) ParseOutput() *go_nmap.NmapRun {
+	sweepXML := fmt.Sprintf("%s.xml", s.Outfile)
+	dat, err := ioutil.ReadFile(sweepXML)
+	if err != nil {
+		utils.Config.Log.LogError(fmt.Sprintf("Error while opening output file: %s", sweepXML))
+		return nil
+	}
+
+	res, err := go_nmap.Parse(dat)
+	if err != nil {
+		utils.Config.Log.LogError("Error while parsing nmap output")
+		return nil
+	}
+	return res
+}
+
+// ---------------------------------------------------------------------------------------
+// SCAN MANAGEMENT
+// ---------------------------------------------------------------------------------------
+func ReportStatusNmap() {
+	ticker := time.Tick(notificationDelay)
+	for {
+		<-ticker
+
+		if len(ScansList) != 0 {
+			i := 0
+			for _, scan := range ScansList {
+				switch {
+				case scan.Status == null:
+					break
+				case scan.Status == failed:
+					utils.Config.Log.LogError(fmt.Sprintf("Nmap failed on host: %s", scan.Target))
+				case scan.Status == in_progress:
+					utils.Config.Log.LogInfo(fmt.Sprintf("Nmap work in progress on host (%s):\t%s", scan.Name, scan.Target))
+					// Update in place, remove finished scans
+					ScansList[i] = scan
+					i++
+				case scan.Status == finished:
+					utils.Config.Log.LogNotify(fmt.Sprintf("Nmap finished on host (%s):\t%s", scan.Name, scan.Target))
+					utils.Config.Log.LogNotify(fmt.Sprintf("Output has been saved at (%s):\t%s", scan.Name, utils.Config.Outfolder))
+				}
+			}
+			// Update in place, remove finished scans
+			ScansList = ScansList[:i]
+		}
+	}
+}
diff --git a/goscan/core/scan/portscan.go b/goscan/core/scan/portscan.go
new file mode 100644
index 0000000..9db16e5
--- /dev/null
+++ b/goscan/core/scan/portscan.go
@@ -0,0 +1,109 @@
+package scan
+
+import (
+	"goscan/core/model"
+	"goscan/core/utils"
+	"sync"
+	"time"
+)
+
+// ---------------------------------------------------------------------------------------
+// CONSTANTS
+// ---------------------------------------------------------------------------------------
+var mutex sync.Mutex
+var notificationDelay time.Duration = time.Duration(utils.Const_notification_delay_unit) * time.Second
+var ScansList = []*NmapScan{}
+
+// ---------------------------------------------------------------------------------------
+// DISPATCHER
+// ---------------------------------------------------------------------------------------
+func ScanPort(target string, kind string) {
+	folder := "portscan"
+
+	// Dispatch scan
+	switch kind {
+	case "TCP-FULL":
+		utils.Config.Log.LogInfo("Starting full TCP port scan")
+		file, nmapArgs := "tcp_full", utils.Const_NMAP_TCP_FULL
+		execScan(file, target, folder, file, nmapArgs)
+	case "TCP-STANDARD":
+		utils.Config.Log.LogInfo("Starting top 200 TCP port scan")
+		file, nmapArgs := "tcp_standard", utils.Const_NMAP_TCP_STANDARD
+		execScan(file, target, folder, file, nmapArgs)
+	case "TCP-VULN-SCAN":
+		utils.Config.Log.LogInfo("Starting TCP vuln scan")
+		file, nmapArgs := "tcp_vuln", utils.Const_NMAP_TCP_VULN
+		execScan(file, target, folder, file, nmapArgs)
+	case "UDP-STANDARD":
+		utils.Config.Log.LogInfo("Starting UDP port scan (common ports)")
+		file, nmapArgs := "udp_standard", utils.Const_NMAP_UDP_STANDARD
+		execScan(file, target, folder, file, nmapArgs)
+	default:
+		utils.Config.Log.LogError("Invalid type of scan")
+		return
+	}
+}
+
+// ---------------------------------------------------------------------------------------
+// SCAN LAUNCHER
+// ---------------------------------------------------------------------------------------
+func execScan(name, target, folder, file, nmapArgs string) {
+	for i := 0; i < len(utils.Config.Hosts); i++ {
+		// Scan only if:
+		//   - target is ALL
+		//   - or if host is the selected one
+		if target == "ALL" || target == utils.Config.Hosts[i].Address {
+			go worker(name, &utils.Config.Hosts[i], folder, file, nmapArgs)
+		}
+	}
+}
+
+// ---------------------------------------------------------------------------------------
+// WORKER
+// ---------------------------------------------------------------------------------------
+func worker(name string, h *model.Host, folder string, file string, nmapArgs string) {
+	// Instantiate new NmapScan
+	s := NewScan(name, h.Address, folder, file, nmapArgs)
+	ScansList = append(ScansList, s)
+
+	// Run the scan
+	s.RunNmap()
+
+	// Parse nmap's output
+	res := s.ParseOutput()
+
+	// Extract ports and services
+	for _, record := range res.Hosts {
+		// Extract OS
+		if len(record.Os.OsMatches) != 0 && record.Os.OsMatches[0].Name != "" {
+			mutex.Lock()
+			h.OS = record.Os.OsMatches[0].Name
+			mutex.Unlock()
+		}
+		// Patse ports
+		for _, port := range record.Ports {
+			var tService model.Service
+			var tPort model.Port
+			// Extract service
+			if port.Service.Name != "" {
+				tService = model.Service{
+					Name:    port.Service.Name,
+					Version: port.Service.Version,
+					Product: port.Service.Product,
+					OsType:  port.Service.OsType,
+				}
+			}
+			// Extract port
+			tPort = model.Port{
+				Number:   port.PortId,
+				Protocol: port.Protocol,
+				Status:   port.State.State,
+				Service:  tService,
+			}
+			// If new port, add it to host
+			mutex.Lock()
+			h.AddPort(tPort)
+			mutex.Unlock()
+		}
+	}
+}
diff --git a/goscan/core/scan/sweep.go b/goscan/core/scan/sweep.go
new file mode 100644
index 0000000..a7828e2
--- /dev/null
+++ b/goscan/core/scan/sweep.go
@@ -0,0 +1,100 @@
+package scan
+
+import (
+	"fmt"
+	"goscan/core/model"
+	"goscan/core/utils"
+	"os/exec"
+	"path/filepath"
+	"strings"
+)
+
+// ---------------------------------------------------------------------------------------
+// DISPATCHER
+// ---------------------------------------------------------------------------------------
+func ScanSweep(target string, kind string) {
+	// Dispatch scan
+	switch kind {
+	case "PING":
+		pingSweep(target)
+	case "ARP":
+		arpScan(target)
+	case "ALL":
+		arpScan(target)
+		pingSweep(target)
+	default:
+		utils.Config.Log.LogError("Invalid type of scan")
+		return
+	}
+	// Print hosts on screen
+	utils.ShowHosts()
+}
+
+// ---------------------------------------------------------------------------------------
+// SCANS
+// ---------------------------------------------------------------------------------------
+func pingSweep(target string) {
+	// Create a new Scan and run it
+	utils.Config.Log.LogInfo("Starting ping sweep...")
+	name, folder, file, nmapArgs := "pingsweep", "sweep", "ping", "-n -sn"
+	s := NewScan(name, target, folder, file, nmapArgs)
+	s.RunNmap()
+
+	// Parse nmap's output
+	res := s.ParseOutput()
+
+	// Identify live hosts
+	for _, host := range res.Hosts {
+		status := host.Status.State
+		if status == "up" {
+			addr := host.Addresses[0].Addr
+			temp := model.Host{
+				Address: addr,
+				Status:  status,
+			}
+			AddHost(temp)
+		}
+	}
+	utils.Config.Log.LogInfo("Ping sweep completed!")
+}
+
+func arpScan(target string) {
+	// Directly run netdiscover
+	utils.Config.Log.LogInfo("Starting ARP scan...")
+	outfile := filepath.Join(utils.Config.Outfolder, utils.CleanPath(target), "sweep/netdiscover")
+	cmd := fmt.Sprintf("sudo netdiscover -r %s -P > %s && cat %s | grep -E '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}' | cut -d ' ' -f 2 | sort -u > %s", target, outfile, outfile, outfile)
+
+	// Execute the command
+	utils.Config.Log.LogDebug(fmt.Sprintf("Netdiscover command: %s", cmd))
+	out, err := exec.Command("sh", "-c", cmd).Output()
+	if err != nil {
+		utils.Config.Log.LogError("Failed to run netdiscover")
+		return
+	}
+
+	// Identify live hosts
+	for _, line := range strings.Split(strings.TrimSuffix(string(out[:]), "\n"), "\n") {
+		if line != "" {
+			temp := model.Host{
+				Address: line,
+				Status:  "up",
+			}
+			AddHost(temp)
+		}
+	}
+
+	utils.Config.Log.LogInfo("ARP scan completed!")
+}
+
+// Add port to the list only if it's new
+func AddHost(newHost model.Host) {
+	existing := false
+	for _, h := range utils.Config.Hosts {
+		if h.Address == newHost.Address {
+			existing = true
+		}
+	}
+	if existing == false {
+		utils.Config.Hosts = append(utils.Config.Hosts, newHost)
+	}
+}
diff --git a/goscan/core/utils/logger.go b/goscan/core/utils/logger.go
new file mode 100644
index 0000000..bdd9cfb
--- /dev/null
+++ b/goscan/core/utils/logger.go
@@ -0,0 +1,42 @@
+package utils
+
+import (
+	"fmt"
+	"github.com/fatih/color"
+)
+
+// ---------------------------------------------------------------------------------------
+// LOGGER
+// ---------------------------------------------------------------------------------------
+type Logger struct{}
+
+func InitLogger() *Logger {
+	return &Logger{}
+}
+
+func (l *Logger) LogDebug(message string) {
+	highlight := color.New(color.FgWhite).SprintFunc()
+	reset := color.New(color.FgWhite).SprintFunc()
+	fmt.Println(highlight("[-]"), reset(message))
+}
+
+func (l *Logger) LogInfo(message string) {
+	highlight := color.New(color.FgBlue).SprintFunc()
+	reset := color.New(color.FgWhite).SprintFunc()
+	fmt.Println(highlight("[*]"), reset(message))
+}
+
+func (l *Logger) LogNotify(message string) {
+	highlight := color.New(color.FgGreen).SprintFunc()
+	fmt.Println(highlight("[+]"), highlight(message))
+}
+
+func (l *Logger) LogWarning(message string) {
+	highlight := color.New(color.FgYellow).SprintFunc()
+	fmt.Println(highlight("[?]"), highlight(message))
+}
+
+func (l *Logger) LogError(message string) {
+	highlight := color.New(color.FgRed).SprintFunc()
+	fmt.Println(highlight("[!]"), highlight(message))
+}
diff --git a/goscan/core/utils/net.go b/goscan/core/utils/net.go
new file mode 100644
index 0000000..18c76fa
--- /dev/null
+++ b/goscan/core/utils/net.go
@@ -0,0 +1,30 @@
+package utils
+
+import (
+	"net"
+)
+
+// Parse a string and returns the corresponding CIDR
+func ParseCIDR(s string) string {
+	_, ipv4Net, err := net.ParseCIDR(s)
+	if err != nil {
+		return ""
+	}
+	return ipv4Net.String()
+}
+
+// Returns all the addresses of the local network interfaces
+func ParseLocalIP() map[string]string {
+	// Returns a Map of interface:subnet
+	res := make(map[string]string)
+
+	ifaces, _ := net.Interfaces()
+	for _, i := range ifaces {
+		addrs, _ := i.Addrs()
+		for _, addr := range addrs {
+			res[i.Name] = addr.String()
+			break
+		}
+	}
+	return res
+}
diff --git a/goscan/core/utils/utils.go b/goscan/core/utils/utils.go
new file mode 100644
index 0000000..07ada1d
--- /dev/null
+++ b/goscan/core/utils/utils.go
@@ -0,0 +1,167 @@
+package utils
+
+import (
+	"fmt"
+	"github.com/olekukonko/tablewriter"
+	"goscan/core/model"
+	"os"
+	"os/user"
+	"path/filepath"
+	"strings"
+)
+
+// ---------------------------------------------------------------------------------------
+// CONSTANTS
+// ---------------------------------------------------------------------------------------
+var Config config
+
+var Const_notification_delay_unit = 5
+var Const_example_target_cidr = "127.0.0.1/32"
+var Const_example_target_desc = "Target CIDR or /32 for single target"
+
+// NMAP COMMANDS
+var Const_UDP_PORTS = "19,53,69,79,111,123,135,137,138,161,177,445,500,514,520,1434,1900,5353"
+var Const_NMAP_TCP_FULL = "-Pn -sT -sC -A -T4 -p-"
+var Const_NMAP_TCP_STANDARD = "-Pn -sS -A -T4 --top-ports 200"
+var Const_NMAP_TCP_VULN = "-Pn -sT -sV -p- --script=vulscan/vulscan.nse"
+var Const_NMAP_UDP_STANDARD = fmt.Sprintf("-Pn -sU -sC -A -T4 -p%s", Const_UDP_PORTS)
+
+// WORDLISTS
+var WORDLIST_FUZZ_NAMELIST = "/usr/share/wfuzz/wordlist/fuzzdb/wordlists-user-passwd/names/namelist.txt"
+var WORDLIST_MSF_PWDS = "/usr/share/wordlists/metasploit/unix_passwords.txt"
+var WORDLIST_FINGER_USER = WORDLIST_FUZZ_NAMELIST
+var WORDLIST_FTP_USER = WORDLIST_FUZZ_NAMELIST
+var WORDLIST_SMTP = WORDLIST_FUZZ_NAMELIST
+var WORDLIST_SNMP = "/usr/share/doc/onesixtyone/dict.txt"
+var WORDLIST_HYDRA_SSH_USER = WORDLIST_FUZZ_NAMELIST
+var WORDLIST_HYDRA_SSH_PWD = WORDLIST_MSF_PWDS
+var WORDLIST_HYDRA_FTP_USER = WORDLIST_FUZZ_NAMELIST
+var WORDLIST_HYDRA_FTP_PWD = WORDLIST_MSF_PWDS
+
+// ---------------------------------------------------------------------------------------
+// CONFIG
+// ---------------------------------------------------------------------------------------
+type config struct {
+	Outfolder string
+	Target    string
+	Log       *Logger
+	Hosts     []model.Host
+}
+
+// Initialize global config (db, logger, etc.)
+// From now on it will be accessible as utils.Config
+func InitConfig() {
+	Config = config{}
+	// Initialize logger
+	Config.Log = InitLogger()
+	// Setup Outfolder
+	usr, _ := user.Current()
+	Config.Outfolder = filepath.Join(usr.HomeDir, "goscan")
+	EnsureDir(Config.Outfolder)
+}
+
+// ---------------------------------------------------------------------------------------
+// MANAGE COMMANDS
+// ---------------------------------------------------------------------------------------
+// Tokenize the command line
+func ParseCmd(s string) (string, []string) {
+	// Remove trailing spaces
+	s = strings.TrimSpace(s)
+	if len(s) == 0 {
+		return "", make([]string, 0)
+	}
+	// Tokenize the string
+	tokens := strings.Fields(s)
+	// Get the command (1st word), and args
+	cmd, args := tokens[0], tokens[1:]
+	return cmd, args
+}
+
+// Extract the next argument from command line
+func ParseNextArg(args []string) (string, []string) {
+	if len(args) < 2 {
+		return args[0], make([]string, 0)
+	}
+	return args[0], args[1:]
+}
+
+// ---------------------------------------------------------------------------------------
+// MANAGE FILES
+// ---------------------------------------------------------------------------------------
+// Ensures the program is run as root
+func CheckSudo() {
+	if os.Geteuid() != 0 {
+		Config.Log.LogError("This program need to have root permission to execute nmap for now.")
+		os.Exit(1)
+	}
+}
+
+// Ensure the directory exists, or creeates it otherwise
+func EnsureDir(dir string) {
+	// Create a directory if doesn't exist
+	if _, err := os.Stat(dir); os.IsNotExist(err) {
+		os.MkdirAll(dir, os.ModePerm)
+		Config.Log.LogDebug(fmt.Sprintf("Created directory: %s", dir))
+	}
+}
+
+// Replace slashes with underscores, when the string is used in a path
+func CleanPath(s string) string {
+	return strings.Replace(s, "/", "_", -1)
+
+}
+
+// Given a path and a list of strings, it writes them to file
+func WriteArrayToFile(path string, s []string) {
+	Config.Log.LogDebug(fmt.Sprintf("Writing output to file: %s", path))
+	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0755)
+	if err != nil {
+		Config.Log.LogError("Cannot create file")
+	}
+	defer f.Close()
+
+	sep := "\n"
+	for _, line := range s {
+		if _, err = f.WriteString(line + sep); err != nil {
+			Config.Log.LogError(fmt.Sprintf("Error while writing to file: %s", err))
+		}
+	}
+
+}
+
+// ---------------------------------------------------------------------------------------
+// HOSTS
+// ---------------------------------------------------------------------------------------
+func ShowHosts() {
+	if len(Config.Hosts) == 0 {
+		Config.Log.LogError("No hosts are up!")
+		return
+	}
+
+	Config.Log.LogNotify("The following hosts are up:")
+	table := tablewriter.NewWriter(os.Stdout)
+	table.SetHeader([]string{"Status", "Address", "OS", "Ports"})
+	table.SetRowLine(true)
+	table.SetAlignment(3)
+	table.SetAutoWrapText(false)
+
+	for i := 0; i < len(Config.Hosts); i++ {
+		rStatus := Config.Hosts[i].Status
+		rAddress := Config.Hosts[i].Address
+		rOS := Config.Hosts[i].OS
+
+		if len(Config.Hosts[i].Ports) == 0 {
+			v := []string{rStatus, rAddress, rOS, ""}
+			table.Append(v)
+		} else {
+			rPorts := ""
+			for _, s := range Config.Hosts[i].Ports {
+				rPorts = fmt.Sprintf("%s* %s\n", rPorts, s.String())
+			}
+			v := []string{rStatus, rAddress, rOS, rPorts}
+			table.Append(v)
+		}
+	}
+
+	table.Render()
+}
diff --git a/goscan/main.go b/goscan/main.go
new file mode 100644
index 0000000..4904f84
--- /dev/null
+++ b/goscan/main.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"fmt"
+	"github.com/c-bata/go-prompt"
+	"github.com/fatih/color"
+	"goscan/core/cli"
+	"goscan/core/utils"
+	"strings"
+)
+
+var (
+	author  string
+	version string
+)
+
+// ---------------------------------------------------------------------------------------
+// INIT
+// ---------------------------------------------------------------------------------------
+func showBanner() {
+	name := fmt.Sprintf("goscan (v.%s)", version)
+	banner := `
+_________     ___________________________   __
+__  ____/_______  ___/_  ____/__    |__  | / /
+_  / __ _  __ \____ \_  /    __  /| |_   |/ / 
+/ /_/ / / /_/ /___/ // /___  _  ___ |  /|  /  
+\____/  \____//____/ \____/  /_/  |_/_/ |_/   
+											
+	`
+
+	// Shell width
+	all_lines := strings.Split(banner, "\n")
+	w := len(all_lines[1])
+
+	// Print Centered
+	fmt.Println(banner)
+	color.Green(fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(name))/2, name)))
+	color.Blue(fmt.Sprintf("%[1]*s", -w, fmt.Sprintf("%[1]*s", (w+len(author))/2, author)))
+	fmt.Println()
+}
+
+func initCore() {
+	// Check sudo
+	utils.CheckSudo()
+	// Show banner
+	showBanner()
+	// Initialize global config (db, logger, etc.)
+	// From now on it will be accessible as utils.Config
+	utils.InitConfig()
+}
+
+// ---------------------------------------------------------------------------------------
+// MAIN
+// ---------------------------------------------------------------------------------------
+func main() {
+	// Setup core
+	initCore()
+
+	// Start CLI
+	p := prompt.New(
+		cli.Executor,
+		cli.Completer,
+		prompt.OptionTitle("goscan: Interactive Network Scanner"),
+		prompt.OptionPrefix("[goscan] > "),
+		prompt.OptionInputTextColor(prompt.White),
+	)
+	p.Run()
+}