Initial commit

This commit is contained in:
2026-03-03 13:58:06 -05:00
commit e81de212db
11 changed files with 459 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
# Build outputs
bin/
# IDE files
.idea/
+133
View File
@@ -0,0 +1,133 @@
package main
import (
"errors"
"fmt"
"strings"
)
var startPattern = "DNSHelper <<-> START CONFIG"
var endPattern = "DNSHelper <->> END CONFIG"
func extractLinesToEdit(data *workingData) error {
startIndex := 0
endIndex := 0
for i, line := range data.DefaultContent {
if strings.Contains(line, startPattern) {
startIndex = i
continue
}
if strings.Contains(line, endPattern) {
endIndex = i
break
}
}
var fileModified bool
var fileCorrupted bool
switch {
case startIndex == 0 && endIndex == 0:
fileModified = false
fileCorrupted = false
case startIndex > 0 && endIndex > startIndex:
fileModified = true
fileCorrupted = false
default:
fileModified = true
fileCorrupted = true
}
if fileCorrupted == true {
return errors.New("content has been corrupted")
}
if fileModified == true {
data.PrefixContent = data.DefaultContent[:startIndex]
data.PostfixContent = data.DefaultContent[endIndex+1:]
data.ExistingContent = data.DefaultContent[startIndex+1 : endIndex]
} else {
data.PrefixContent = data.DefaultContent
data.PostfixContent = make([]string, 0)
data.ExistingContent = make([]string, 0)
}
data.NewContent = make([]string, 0)
//fmt.Println("PrefixContent:")
//for _, line := range data.PrefixContent {
// fmt.Println(line)
//}
//fmt.Println("ExistingContent:")
//for _, line := range data.ExistingContent {
// fmt.Println(line)
//}
//fmt.Println("PostfixContent:")
//for _, line := range data.PostfixContent {
// fmt.Println(line)
//}
return nil
}
func removeHostsFromExistingContent(data *workingData) {
var updatedContent []string
for _, host := range data.Hosts {
for _, line := range data.ExistingContent {
if !strings.Contains(line, host) {
updatedContent = append(updatedContent, line)
}
}
}
data.NewContent = updatedContent
}
func addHostsToWorkingData(data *workingData, hosts *string) {
hostToAddList := strings.Split(*hosts, ",")
var newHosts []string
for _, host := range hostToAddList {
newHosts = append(newHosts, host)
}
data.Hosts = newHosts
}
func addResolversToWorkingData(data *workingData, resolvers *string) {
resolverToAddList := strings.Split(*resolvers, ",")
var newResolvers []string
for _, resolver := range resolverToAddList {
newResolvers = append(newResolvers, resolver)
}
data.Resolvers = newResolvers
}
func addNameToIPMappingsToNewContent(data *workingData) {
for _, host := range data.Hosts {
for _, resolver := range data.Resolvers {
ips := lookupIP(host, resolver)
if len(ips) > 0 {
for _, ip := range ips {
data.NewContent = append(data.NewContent, fmt.Sprintf("%s\t%s", ip, host))
}
}
}
}
}
func slicesEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func dedupeSlice(slice []string) []string {
newSlice := make([]string, 0, len(slice))
seen := make(map[string]bool)
for _, value := range slice {
if !seen[value] {
seen[value] = true
newSlice = append(newSlice, value)
}
}
return newSlice
}
+57
View File
@@ -0,0 +1,57 @@
package main
import (
"fmt"
"github.com/miekg/dns"
)
func lookupIP(hostname string, resolver string) []string {
c := dns.Client{}
m := dns.Msg{}
m.SetQuestion(dns.Fqdn(hostname), dns.TypeA)
r, _, err := c.Exchange(&m, resolver+":53")
if err != nil {
return nil
}
if r.Rcode != dns.RcodeSuccess {
fmt.Println("Error: querying " + resolver + " for A record of " + hostname + " failed with code " + dns.RcodeToString[r.Rcode])
return nil
}
ips := make([]string, 0)
for _, ans := range r.Answer {
if rec, ok := ans.(*dns.A); ok {
ips = append(ips, rec.A.String())
}
}
if len(ips) > 0 {
return ips
}
c = dns.Client{}
m = dns.Msg{}
m.SetQuestion(dns.Fqdn(hostname), dns.TypeCNAME)
r, _, err = c.Exchange(&m, resolver+":53")
if err != nil {
return nil
}
if r.Rcode != dns.RcodeSuccess {
fmt.Println("Error: querying " + resolver + " for CNAME record of " + hostname + " failed with code " + dns.RcodeToString[r.Rcode])
return nil
}
for _, ans := range r.Answer {
if rec, ok := ans.(*dns.CNAME); ok {
ips = lookupIP(rec.Target, resolver)
for _, ip := range ips {
ips = append(ips, ip)
}
}
}
if len(ips) > 0 {
return ips
}
fmt.Println("Error: no IP addresses found for " + hostname)
return nil
}
+73
View File
@@ -0,0 +1,73 @@
package main
import (
"bufio"
"os"
)
func fileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func getFileContentLines(path string) ([]string, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return lines, nil
}
func writeOverContentInFile(path string, lines []string) error {
file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
for _, line := range lines {
_, err := writer.WriteString(line + "\n")
if err != nil {
return err
}
}
err = writer.Flush()
if err != nil {
return err
}
return nil
}
func writeHostsFile(data *workingData) error {
var newFileContent []string
newFileContent = append(newFileContent, data.PrefixContent...)
if len(data.NewContent) > 0 {
data.NewContent = dedupeSlice(data.NewContent)
newFileContent = append(newFileContent, "# "+startPattern)
newFileContent = append(newFileContent, data.NewContent...)
newFileContent = append(newFileContent, "# "+endPattern)
}
newFileContent = append(newFileContent, data.PostfixContent...)
if slicesEqual(newFileContent, data.DefaultContent) == false {
err := writeOverContentInFile(data.HostsFileLocation, newFileContent)
if err != nil {
return err
}
}
return nil
}
+18
View File
@@ -0,0 +1,18 @@
//go:build darwin
package main
import "errors"
func getHostsFileLocation() (string, error) {
var hostsFileLocation string
hostsFileLocation = "/etc/hosts"
exists, err := fileExists(hostsFileLocation)
if err != nil {
term(err)
}
if exists == false {
term(errors.New("hosts file does not exist at " + hostsFileLocation))
}
return hostsFileLocation, nil
}
+18
View File
@@ -0,0 +1,18 @@
//go:build linux
package main
import "errors"
func getHostsFileLocation() (string, error) {
var hostsFileLocation string
hostsFileLocation = "/etc/hosts"
exists, err := fileExists(hostsFileLocation)
if err != nil {
term(err)
}
if exists == false {
term(errors.New("hosts file does not exist at " + hostsFileLocation))
}
return hostsFileLocation, nil
}
+27
View File
@@ -0,0 +1,27 @@
//go:build windows
package main
import (
"errors"
"os"
)
func getHostsFileLocation() (string, error) {
//return "hosts", nil
var hostsFileLocation string
value, envExists := os.LookupEnv("SystemRoot")
if envExists {
hostsFileLocation = value + "\\System32\\drivers\\etc\\hosts"
} else {
hostsFileLocation = "C:\\Windows\\System32\\drivers\\etc\\hosts"
}
exists, err := fileExists(hostsFileLocation)
if err != nil {
return "", err
}
if exists == false {
return "", errors.New("file does not exist: " + hostsFileLocation)
}
return hostsFileLocation, nil
}
+11
View File
@@ -0,0 +1,11 @@
module dns-helper
go 1.18
require (
github.com/miekg/dns v1.1.59 // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/tools v0.19.0 // indirect
)
+10
View File
@@ -0,0 +1,10 @@
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
+94
View File
@@ -0,0 +1,94 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
)
var fileName = filepath.Base(os.Args[0])
func init() {
fmt.Println("DNSHelper v1.0")
fmt.Println("Copyright (c) 2024 Emberkom LLC")
fmt.Println("")
if len(os.Args) < 2 {
printUsage()
defer os.Exit(1)
}
}
func printUsage() {
fmt.Println("This utility will update the local hosts file with DNS entries obtained by the specified DNS server.")
fmt.Println("Usage: " + fileName + " [add|delete] -host hostname [-server dns.example.com]")
fmt.Println("Example:")
fmt.Println(" " + fileName + " add -host xyz.acme.com -server dns.example.com")
fmt.Println(" This will use dns.example.com to find and add all IP addresses for xyz.acme.com to the local hosts file.")
fmt.Println(" " + fileName + " delete -host hostname.example.com")
fmt.Println(" This will delete all entries for hostname.example.com from the local hosts file.")
fmt.Println(" " + fileName + " delete all")
fmt.Println(" This will delete all entries from the local hosts file that were added by this utility.")
fmt.Println("Note: Adding a hostname will first remove all entries in the hosts file that match the same hostname.")
fmt.Println(" This utility will only remove entries from the hosts file that it added.")
}
func term(err error) {
fmt.Println("Error: " + err.Error())
defer os.Exit(1)
}
func main() {
modePing := flag.NewFlagSet("ping", flag.ExitOnError)
modeAdd := flag.NewFlagSet("add", flag.ExitOnError)
hostToAdd := modeAdd.String("host", "", "Comma separated list of hostnames to add")
dnsResolver := modeAdd.String("server", "", "DNS resolver to use")
modeDelete := flag.NewFlagSet("delete", flag.ExitOnError)
hostToDelete := modeDelete.String("host", "", "Comma separated list of hostnames to delete")
data := new(workingData)
var err error
data.HostsFileLocation, err = getHostsFileLocation()
if err != nil {
term(err)
}
data.DefaultContent, err = getFileContentLines(data.HostsFileLocation)
if err != nil {
term(err)
}
if err = extractLinesToEdit(data); err != nil {
term(err)
}
switch strings.ToLower(os.Args[1]) {
case "a", "add":
err = modeAdd.Parse(os.Args[2:])
if err != nil {
term(err)
}
addHostsToWorkingData(data, hostToAdd)
addResolversToWorkingData(data, dnsResolver)
removeHostsFromExistingContent(data)
addNameToIPMappingsToNewContent(data)
case "d", "del", "delete":
err = modeDelete.Parse(os.Args[2:])
if err != nil {
term(err)
}
if strings.ToLower(*hostToDelete) != "all" && strings.ToLower(*hostToDelete) != "a" {
addHostsToWorkingData(data, hostToDelete)
removeHostsFromExistingContent(data)
}
default:
printUsage()
}
err = writeHostsFile(data)
if err != nil {
term(err)
}
}
+13
View File
@@ -0,0 +1,13 @@
package main
type workingData struct {
HostsFileLocation string
IsNew bool
Hosts []string
Resolvers []string
DefaultContent []string
PrefixContent []string
NewContent []string
ExistingContent []string
PostfixContent []string
}