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:
tobias
2025-08-24 19:50:00 +02:00
parent 9518290544
commit 619b0bc432
124 changed files with 1063 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
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)
}

View File

@@ -0,0 +1,140 @@
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)
}
}

View File

@@ -0,0 +1,3 @@
module git.ktf.ninja/tabledevil/goinfo
go 1.22.5

View File

@@ -0,0 +1,52 @@
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.

View File

@@ -0,0 +1,376 @@
// 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.

View File

@@ -0,0 +1,502 @@
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
}

View File

@@ -0,0 +1,75 @@
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)
}
}