Restructure repository: organize tools by purpose, create what search tool
- Move single-file tools to tools/ organized by category (security, forensics, data, etc.) - Move multi-file projects to projects/ (go-tools, puzzlebox, timesketch, rust-tools) - Move system scripts to scripts/ (proxy, display, setup, windows) - Organize config files in config/ (shell, visidata, applications) - Move experimental tools to archive/experimental - Create 'what' fuzzy search tool with progressive enhancement (ollama->fzf->grep) - Add initial metadata database for intelligent tool discovery - Preserve git history using 'git mv' commands
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func csvToJson(inputSource *os.File) {
|
||||
csvReader := csv.NewReader(inputSource)
|
||||
headers, err := csvReader.Read()
|
||||
if err != nil {
|
||||
log.Fatal("Failed to read headers: ", err)
|
||||
}
|
||||
for {
|
||||
record, err := csvReader.Read()
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
break
|
||||
}
|
||||
log.Fatal("Failed to read the data: ", err)
|
||||
}
|
||||
if len(record) == len(headers) {
|
||||
jsonData := make(map[string]string)
|
||||
for i, value := range record {
|
||||
jsonData[headers[i]] = value
|
||||
}
|
||||
jsonOutput, err := json.Marshal(jsonData)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to convert to json: ", err)
|
||||
}
|
||||
fmt.Println(string(jsonOutput))
|
||||
} else {
|
||||
log.Fatal("The number of columns in the record is not equal to the number of headers")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
inputSource := os.Stdin
|
||||
flag.Parse()
|
||||
var err error
|
||||
if flag.NArg() > 0 {
|
||||
inputSource, err = os.Open(flag.Args()[0])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open file: %v\n", err)
|
||||
}
|
||||
defer inputSource.Close()
|
||||
}
|
||||
csvToJson(inputSource)
|
||||
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func printUsage() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [options] <range specification> [file]\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "Options:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintln(os.Stderr, "\nRange specification examples:")
|
||||
fmt.Fprintln(os.Stderr, "10,20 Reads lines 10 to 20 from the input")
|
||||
fmt.Fprintln(os.Stderr, "15:+5 Reads 5 lines starting from line 15")
|
||||
fmt.Fprintln(os.Stderr, "3 Reads from line 3 to the end of the file")
|
||||
fmt.Fprintln(os.Stderr, "+2 Reads the first 2 lines")
|
||||
fmt.Fprintln(os.Stderr, "Please ensure you input valid range specifications and file paths.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func parseArgs(args []string) (int, int, string, error) {
|
||||
var filename string
|
||||
var start, length, end int
|
||||
found := false
|
||||
|
||||
// Concatenate all arguments to a single string for easier parsing
|
||||
joinedArgs := strings.Join(args, " ")
|
||||
|
||||
// Regular expression to match ranges and numbers
|
||||
rangeRegex := regexp.MustCompile(`((\d+)?([ :,;-]))(\+)?(\d+)`)
|
||||
range2Regex := regexp.MustCompile(`((\d+)([ :,;-]))`)
|
||||
range3Regex := regexp.MustCompile(`(\+)?(\d+)`)
|
||||
|
||||
matches := rangeRegex.FindStringSubmatch(joinedArgs)
|
||||
|
||||
if matches != nil {
|
||||
//check if start was defined
|
||||
if matches[2] != "" {
|
||||
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
|
||||
} else {
|
||||
start = 1
|
||||
}
|
||||
if matches[4] == "+" { // Check if it's a relative length
|
||||
length, _ = strconv.Atoi(matches[5]) // Convert length to integer
|
||||
end = start + length - 1
|
||||
} else {
|
||||
end, _ = strconv.Atoi(matches[5]) // Convert end line to integer
|
||||
}
|
||||
// Remove the matched part from the arguments
|
||||
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
|
||||
found = true
|
||||
} else {
|
||||
matches = range2Regex.FindStringSubmatch(joinedArgs)
|
||||
if matches != nil {
|
||||
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
|
||||
end = -1
|
||||
// Remove the matched part from the arguments
|
||||
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
|
||||
found = true
|
||||
} else {
|
||||
matches = range3Regex.FindStringSubmatch(joinedArgs)
|
||||
if matches != nil {
|
||||
if matches[1] == "+" { // Check if it's a relative length
|
||||
length, _ := strconv.Atoi(matches[2]) // Convert length to integer
|
||||
start = 1
|
||||
end = start + length - 1
|
||||
} else { // Otherwise convert it to an absolute length
|
||||
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
|
||||
end = -1
|
||||
// Remove the matched part from the arguments
|
||||
}
|
||||
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
|
||||
found = true
|
||||
} else {
|
||||
found = false
|
||||
printUsage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up and identify the filename, if present
|
||||
joinedArgs = strings.TrimSpace(joinedArgs)
|
||||
if joinedArgs != "" && !found {
|
||||
// If we didn't find numbers, interpret the remaining as filename
|
||||
filename = joinedArgs
|
||||
} else if joinedArgs != "" {
|
||||
// Otherwise, interpret any non-empty remaining part as filename
|
||||
filename = joinedArgs
|
||||
}
|
||||
|
||||
if !found {
|
||||
return 0, 0, "", fmt.Errorf("no valid range or line number found")
|
||||
}
|
||||
|
||||
return start, end, filename, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) < 1 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
startIndex, endIndex, filename, err := parseArgs(args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Detemine the input source (file or stdin)
|
||||
inputSource := os.Stdin
|
||||
if filename != "" {
|
||||
inputSource, err = os.Open(filename)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open file: %v\n", err)
|
||||
}
|
||||
defer inputSource.Close()
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(inputSource)
|
||||
//print all lines from line buner start to linenumber end unless linenumber end is -1
|
||||
for i := 1; scanner.Scan(); i++ {
|
||||
if startIndex <= i && (i <= endIndex || endIndex == -1) {
|
||||
fmt.Println(scanner.Text())
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module git.ktf.ninja/tabledevil/goinfo
|
||||
|
||||
go 1.22.5
|
||||
@@ -1,52 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
func getHostname() string {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Println("Could not retrieve Hostname")
|
||||
return ""
|
||||
} else {
|
||||
return hostname
|
||||
}
|
||||
}
|
||||
|
||||
func getUsernameDomain() string {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return user.Username
|
||||
}
|
||||
|
||||
func getIP(hostname string) string {
|
||||
addrs, err := net.LookupIP(hostname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return addrs[0].String()
|
||||
}
|
||||
|
||||
func getGroups() []int {
|
||||
groups, err := os.Getgroups()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("hostname: ", getHostname())
|
||||
fmt.Println("username: ", getUsernameDomain())
|
||||
fmt.Println("groups: ", getGroups())
|
||||
fmt.Println("google: ", getIP("www.google.de"))
|
||||
|
||||
}
|
||||
Binary file not shown.
@@ -1,376 +0,0 @@
|
||||
// ipgrep.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IPInfo holds the data we want from ipinfo.io
|
||||
type IPInfo struct {
|
||||
IP string `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
City string `json:"city"`
|
||||
Region string `json:"region"`
|
||||
Country string `json:"country"`
|
||||
Org string `json:"org"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Command-line flags.
|
||||
var (
|
||||
sortFlag bool
|
||||
uniqFlag bool
|
||||
macFlag bool
|
||||
pingable bool
|
||||
resolveFlag bool
|
||||
lookupFlag bool
|
||||
fileName string
|
||||
)
|
||||
flag.BoolVar(&uniqFlag, "u", false, "only show uniq IPs/MACs (implies -s)")
|
||||
flag.BoolVar(&sortFlag, "s", false, "sort output")
|
||||
flag.BoolVar(&macFlag, "m", false, "grep MAC-IDs instead of IPs")
|
||||
flag.BoolVar(&pingable, "p", false, "only show 'pingable' entries (MACs still beta)")
|
||||
flag.BoolVar(&resolveFlag, "r", false, "resolve (uses host for ip and arping for mac)")
|
||||
flag.BoolVar(&lookupFlag, "l", false, "lookup ip info using ipinfo.io and output CSV: ip,country,region,city,org,hostname")
|
||||
flag.StringVar(&fileName, "f", "", "input file")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [-u] [-s] [-m] [-p] [-r] [-l] [-f filename] [file...]\n", os.Args[0])
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// If pingable is set, force sorting and uniqueness.
|
||||
if pingable || lookupFlag {
|
||||
sortFlag = true
|
||||
uniqFlag = true
|
||||
}
|
||||
|
||||
if lookupFlag && macFlag {
|
||||
fmt.Fprintln(os.Stderr, "Lookup mode (-l) only works for IP addresses, not MAC addresses.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Regular expressions for IPs or MACs.
|
||||
var pattern string
|
||||
if macFlag {
|
||||
// Supports MAC formats: xx:xx:xx:xx:xx:xx or xxxx.xxxx.xxxx
|
||||
pattern = `(([a-fA-F0-9]{2}[:-]){5}[a-fA-F0-9]{2})|([a-fA-F0-9]{4}\.[a-fA-F0-9]{4}\.[a-fA-F0-9]{4})`
|
||||
} else {
|
||||
// Matches valid IPv4 addresses.
|
||||
pattern = `(((25[0-5])|(2[0-4][0-9])|([0-1]?\d?\d))\.){3}((25[0-5])|(2[0-4][0-9])|([0-1]?\d?\d))`
|
||||
}
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error compiling regex: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Read input from -f file, extra args, or stdin.
|
||||
var inputData []byte
|
||||
if fileName != "" {
|
||||
inputData, err = ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading file %s: %v\n", fileName, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if flag.NArg() > 0 {
|
||||
var buf bytes.Buffer
|
||||
for _, fname := range flag.Args() {
|
||||
data, err := ioutil.ReadFile(fname)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading file %s: %v\n", fname, err)
|
||||
continue
|
||||
}
|
||||
buf.Write(data)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
inputData = buf.Bytes()
|
||||
} else {
|
||||
inputData, err = io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter matches using the regex.
|
||||
matches := re.FindAllString(string(inputData), -1)
|
||||
if matches == nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if sortFlag {
|
||||
sort.Strings(matches)
|
||||
}
|
||||
if uniqFlag {
|
||||
matches = unique(matches)
|
||||
}
|
||||
|
||||
if pingable {
|
||||
matches = filterPingable(matches, macFlag)
|
||||
if sortFlag {
|
||||
sort.Strings(matches)
|
||||
}
|
||||
}
|
||||
|
||||
// If lookup flag is set, perform ipinfo.io lookups with caching.
|
||||
if lookupFlag {
|
||||
cache := loadCache()
|
||||
var cacheMu sync.Mutex
|
||||
results := lookupIPInfo(matches, cache, &cacheMu)
|
||||
|
||||
// Sort the results by IP.
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].IP < results[j].IP
|
||||
})
|
||||
|
||||
// Save the updated cache.
|
||||
saveCache(cache)
|
||||
|
||||
// Output CSV using csv.Writer for proper CSV formatting.
|
||||
w := csv.NewWriter(os.Stdout)
|
||||
// Write header.
|
||||
if err := w.Write([]string{"ip", "country", "region", "city", "org", "hostname"}); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing CSV header: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, info := range results {
|
||||
record := []string{
|
||||
info.IP,
|
||||
info.Country,
|
||||
info.Region,
|
||||
info.City,
|
||||
info.Org,
|
||||
info.Hostname,
|
||||
}
|
||||
if err := w.Write(record); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing CSV record: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
if err := w.Error(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error flushing CSV data: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// If resolve flag is set, perform resolution.
|
||||
if resolveFlag {
|
||||
results := resolveEntries(matches, macFlag)
|
||||
for _, r := range results {
|
||||
fmt.Print(r)
|
||||
if !macFlag {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, just output the matches.
|
||||
for _, m := range matches {
|
||||
fmt.Println(m)
|
||||
}
|
||||
}
|
||||
|
||||
// unique removes duplicate strings from a slice.
|
||||
func unique(input []string) []string {
|
||||
seen := make(map[string]struct{})
|
||||
var result []string
|
||||
for _, s := range input {
|
||||
if _, ok := seen[s]; !ok {
|
||||
seen[s] = struct{}{}
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// filterPingable runs ping (or arping) concurrently on each entry.
|
||||
func filterPingable(entries []string, mac bool) []string {
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var result []string
|
||||
|
||||
for _, entry := range entries {
|
||||
wg.Add(1)
|
||||
go func(e string) {
|
||||
defer wg.Done()
|
||||
if isPingable(e, mac) {
|
||||
mu.Lock()
|
||||
result = append(result, e)
|
||||
mu.Unlock()
|
||||
}
|
||||
}(entry)
|
||||
}
|
||||
wg.Wait()
|
||||
return result
|
||||
}
|
||||
|
||||
// isPingable tests if an entry is reachable using ping (or arping for MACs).
|
||||
func isPingable(entry string, mac bool) bool {
|
||||
var cmd *exec.Cmd
|
||||
if mac {
|
||||
cmd = exec.Command("arping", "-c", "1", "-w", "5000000", entry)
|
||||
} else {
|
||||
cmd = exec.Command("ping", "-c", "1", "-w", "1", entry)
|
||||
}
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
err := cmd.Run()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// resolveEntries performs resolution via external commands.
|
||||
func resolveEntries(entries []string, mac bool) []string {
|
||||
var wg sync.WaitGroup
|
||||
results := make([]string, len(entries))
|
||||
|
||||
for i, entry := range entries {
|
||||
wg.Add(1)
|
||||
go func(i int, e string) {
|
||||
defer wg.Done()
|
||||
if mac {
|
||||
cmd := exec.Command("arping", "-q", "-c", "1", "-w", "5000000", e)
|
||||
if err := cmd.Run(); err == nil {
|
||||
cmd2 := exec.Command("arping", "-c", "1", e)
|
||||
out, err := cmd2.CombinedOutput()
|
||||
if err == nil {
|
||||
results[i] = string(out)
|
||||
} else {
|
||||
results[i] = e + "\n"
|
||||
}
|
||||
} else {
|
||||
results[i] = e + "\n"
|
||||
}
|
||||
} else {
|
||||
cmd := exec.Command("host", e)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
// Extract the hostname via regex (similar to grep -Po '(?<=pointer ).*')
|
||||
reHost := regexp.MustCompile(`(?i)(?<=pointer\s)(\S+)`)
|
||||
match := reHost.FindString(string(out))
|
||||
if match != "" {
|
||||
results[i] = fmt.Sprintf("%s %s", e, match)
|
||||
} else {
|
||||
results[i] = e
|
||||
}
|
||||
} else {
|
||||
results[i] = e
|
||||
}
|
||||
}
|
||||
}(i, entry)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
// lookupIPInfo queries ipinfo.io for each IP concurrently,
|
||||
// checking a local cache before going to the network.
|
||||
// It returns a slice of IPInfo.
|
||||
func lookupIPInfo(entries []string, cache map[string]IPInfo, cacheMu *sync.Mutex) []IPInfo {
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
var results []IPInfo
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
for _, ip := range entries {
|
||||
wg.Add(1)
|
||||
go func(ip string) {
|
||||
defer wg.Done()
|
||||
// Check cache first.
|
||||
cacheMu.Lock()
|
||||
info, found := cache[ip]
|
||||
cacheMu.Unlock()
|
||||
if found {
|
||||
mu.Lock()
|
||||
results = append(results, info)
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Not in cache; perform HTTP lookup.
|
||||
url := fmt.Sprintf("https://ipinfo.io/%s", ip)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var newInfo IPInfo
|
||||
if err := json.Unmarshal(body, &newInfo); err != nil {
|
||||
return
|
||||
}
|
||||
// Only add valid responses.
|
||||
if newInfo.IP == "" {
|
||||
return
|
||||
}
|
||||
// Update cache.
|
||||
cacheMu.Lock()
|
||||
cache[ip] = newInfo
|
||||
cacheMu.Unlock()
|
||||
mu.Lock()
|
||||
results = append(results, newInfo)
|
||||
mu.Unlock()
|
||||
}(ip)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
// loadCache reads the cache from ~/.ipgrep.db (if present)
|
||||
// and returns it as a map[string]IPInfo.
|
||||
func loadCache() map[string]IPInfo {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return make(map[string]IPInfo)
|
||||
}
|
||||
cachePath := home + "/.ipgrep.db"
|
||||
data, err := ioutil.ReadFile(cachePath)
|
||||
if err != nil {
|
||||
// File doesn't exist or can't be read, start with an empty cache.
|
||||
return make(map[string]IPInfo)
|
||||
}
|
||||
var cache map[string]IPInfo
|
||||
if err := json.Unmarshal(data, &cache); err != nil {
|
||||
// If unmarshal fails, use an empty cache.
|
||||
return make(map[string]IPInfo)
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
// saveCache writes the cache map to ~/.ipgrep.db.
|
||||
func saveCache(cache map[string]IPInfo) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cachePath := home + "/.ipgrep.db"
|
||||
data, err := json.MarshalIndent(cache, "", " ")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error marshaling cache: %v\n", err)
|
||||
return
|
||||
}
|
||||
if err := ioutil.WriteFile(cachePath, data, 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing cache file: %v\n", err)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,502 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Software holds information about installed software
|
||||
type Software struct {
|
||||
Source string `json:"source"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
InstallDate string `json:"install_date"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var softwareList []Software
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
softwareList = append(softwareList, enumerateWindows()...)
|
||||
} else if runtime.GOOS == "linux" {
|
||||
softwareList = append(softwareList, enumerateLinux()...)
|
||||
}
|
||||
|
||||
output, err := json.MarshalIndent(softwareList, "", " ")
|
||||
if err != nil {
|
||||
log.Println("Error marshalling software list:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
|
||||
// Add to the enumerateWindows function
|
||||
func enumerateWindows() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
// Winget
|
||||
softwareList = append(softwareList, enumerateWinget()...)
|
||||
|
||||
// Chocolatey
|
||||
softwareList = append(softwareList, enumerateChocolatey()...)
|
||||
|
||||
// Scoop
|
||||
softwareList = append(softwareList, enumerateScoop()...)
|
||||
|
||||
// Common Windows Installations
|
||||
softwareList = append(softwareList, enumerateCommonWindows()...)
|
||||
|
||||
// NuGet
|
||||
softwareList = append(softwareList, enumerateNuGet()...)
|
||||
|
||||
// Microsoft Store
|
||||
softwareList = append(softwareList, enumerateMicrosoftStore()...)
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
// Add to the enumerateLinux function
|
||||
func enumerateLinux() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
// APT
|
||||
softwareList = append(softwareList, enumerateApt()...)
|
||||
|
||||
// Snap
|
||||
softwareList = append(softwareList, enumerateSnap()...)
|
||||
|
||||
// Pip
|
||||
softwareList = append(softwareList, enumeratePip()...)
|
||||
|
||||
// NPM
|
||||
softwareList = append(softwareList, enumerateNpm()...)
|
||||
|
||||
// Flatpak
|
||||
softwareList = append(softwareList, enumerateFlatpak()...)
|
||||
|
||||
// RPM
|
||||
softwareList = append(softwareList, enumerateRpm()...)
|
||||
|
||||
// Pacman
|
||||
softwareList = append(softwareList, enumeratePacman()...)
|
||||
|
||||
// Homebrew
|
||||
softwareList = append(softwareList, enumerateHomebrew()...)
|
||||
|
||||
// Conda
|
||||
softwareList = append(softwareList, enumerateConda()...)
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateNuGet() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("nuget", "list", "-AllVersions")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating NuGet packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "nuget",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateMicrosoftStore() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("powershell", "Get-AppxPackage", "|", "Select-Object", "Name,PackageFullName,InstallDate")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Microsoft Store packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "microsoft store",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
InstallDate: parseInstallDate(parts[2]),
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateFlatpak() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("flatpak", "list")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Flatpak packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "flatpak",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateRpm() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("rpm", "-qa", "--queryformat", "%{NAME} %{VERSION}\n")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating RPM packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "rpm",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumeratePacman() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("pacman", "-Q")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Pacman packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "pacman",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateHomebrew() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("brew", "list", "--versions")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Homebrew packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "homebrew",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateConda() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("conda", "list", "--json")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Conda packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
var condaPackages []map[string]interface{}
|
||||
err = json.Unmarshal([]byte(output), &condaPackages)
|
||||
if err != nil {
|
||||
log.Println("Error parsing Conda JSON output:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
for _, pkg := range condaPackages {
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "conda",
|
||||
Name: pkg["name"].(string),
|
||||
Version: pkg["version"].(string),
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func runCommand(cmd string, args ...string) (string, error) {
|
||||
out, err := exec.Command(cmd, args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
func parseInstallDate(dateString string) string {
|
||||
layout := "2006-01-02"
|
||||
date, err := time.Parse(layout, dateString)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return date.Format(layout)
|
||||
}
|
||||
|
||||
func enumerateWinget() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("winget", "list", "--source", "winget")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Winget packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := regexp.MustCompile(`\s+`).Split(line, -1)
|
||||
if len(parts) < 4 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "winget",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateChocolatey() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("choco", "list", "--local-only")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Chocolatey packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Split(line, "|")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "chocolatey",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateScoop() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("scoop", "list")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Scoop packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "Installed apps") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "scoop",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateCommonWindows() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("powershell", "Get-WmiObject", "-Class", "Win32_Product", "|", "Select-Object", "Name,Version,InstallDate")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating common Windows installations:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "common windows installation",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
InstallDate: parseInstallDate(parts[2]),
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateApt() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("dpkg-query", "-W", "-f=${binary:Package} ${Version} ${Installed-Size}\n")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating APT packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "apt",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateSnap() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("snap", "list")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Snap packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
lines := strings.Split(output, "\n")
|
||||
for _, line := range lines {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) < 3 {
|
||||
continue
|
||||
}
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "snap",
|
||||
Name: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumeratePip() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("pip", "list", "--format=json")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating Pip packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
var pipPackages []map[string]string
|
||||
err = json.Unmarshal([]byte(output), &pipPackages)
|
||||
if err != nil {
|
||||
log.Println("Error parsing Pip JSON output:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
for _, pkg := range pipPackages {
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "pip",
|
||||
Name: pkg["name"],
|
||||
Version: pkg["version"],
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
|
||||
func enumerateNpm() []Software {
|
||||
var softwareList []Software
|
||||
|
||||
output, err := runCommand("npm", "list", "-g", "--depth=0", "--json")
|
||||
if err != nil {
|
||||
log.Println("Error enumerating NPM packages:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
var npmPackages map[string]interface{}
|
||||
err = json.Unmarshal([]byte(output), &npmPackages)
|
||||
if err != nil {
|
||||
log.Println("Error parsing NPM JSON output:", err)
|
||||
return softwareList
|
||||
}
|
||||
|
||||
dependencies := npmPackages["dependencies"].(map[string]interface{})
|
||||
for name, info := range dependencies {
|
||||
infoMap := info.(map[string]interface{})
|
||||
version := infoMap["version"].(string)
|
||||
softwareList = append(softwareList, Software{
|
||||
Source: "npm",
|
||||
Name: name,
|
||||
Version: version,
|
||||
})
|
||||
}
|
||||
|
||||
return softwareList
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func hashLine(s string) uint32 {
|
||||
hasher := fnv.New32a()
|
||||
hasher.Write([]byte(s))
|
||||
return hasher.Sum32()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Define command line flags
|
||||
reverse := flag.Bool("d", false, "Print only lines that appear more than once.")
|
||||
help := flag.Bool("h", false, "Display help and usage information.")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
|
||||
fmt.Println("This program reads from a file or standard input, deduplicates lines, and outputs the results.")
|
||||
fmt.Println("Options:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Println("Example usage:")
|
||||
fmt.Println("\t", os.Args[0], "[options] [filename]")
|
||||
fmt.Println("\t", os.Args[0], "-d filename # Only print duplicates")
|
||||
fmt.Println("\t", "cat /some/text/file |", os.Args[0], "# Read from standard input")
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
// Check for help flag
|
||||
if *help {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Detemine the input source (file or stdin)
|
||||
inputSource := os.Stdin
|
||||
var err error
|
||||
if flag.NArg() > 0 {
|
||||
inputSource, err = os.Open(flag.Args()[0])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open file: %v\n", err)
|
||||
}
|
||||
defer inputSource.Close()
|
||||
}
|
||||
seenLines := make(map[uint32]int)
|
||||
scanner := bufio.NewScanner(inputSource)
|
||||
|
||||
//Readin lines
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
hash := hashLine(line)
|
||||
seenLines[hash]++
|
||||
|
||||
if *reverse {
|
||||
// Print only lines that appear more than once
|
||||
if seenLines[hash] > 1 {
|
||||
fmt.Println(line)
|
||||
}
|
||||
} else {
|
||||
// Normal mode, print only unique lines
|
||||
if seenLines[hash] == 1 {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
//Check for errors during scanning
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Fatalf("Failed to read input: %v\n", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user