From 7f9dc1660dca9047f5627c6a7b4083a6f529d991 Mon Sep 17 00:00:00 2001 From: Yevhen Vydolob Date: Thu, 12 Sep 2024 09:56:35 +0300 Subject: [PATCH] Check 'hosts' file records, during handling DNS requests This implementation use 'libhosty' lib to read and search for hosts file records. It reads 'hosts' file first and adds file watcher to track changes in 'hosts' file Signed-off-by: Yevhen Vydolob --- go.mod | 1 + go.sum | 2 + pkg/services/dns/dns.go | 28 + pkg/services/dns/hosts_file.go | 94 ++ pkg/services/dns/hosts_file_test.go | 41 + .../github.com/areYouLazy/libhosty/.gitignore | 5 + vendor/github.com/areYouLazy/libhosty/LICENSE | 201 +++++ .../github.com/areYouLazy/libhosty/README.md | 167 ++++ vendor/github.com/areYouLazy/libhosty/TODO.md | 4 + .../areYouLazy/libhosty/coverage_badge.png | Bin 0 -> 2202 bytes .../github.com/areYouLazy/libhosty/errors.go | 38 + .../areYouLazy/libhosty/formatter.go | 33 + .../github.com/areYouLazy/libhosty/helper.go | 25 + .../areYouLazy/libhosty/libhosty.go | 846 ++++++++++++++++++ .../github.com/areYouLazy/libhosty/parser.go | 112 +++ .../areYouLazy/libhosty/templates.go | 52 ++ vendor/modules.txt | 3 + 17 files changed, 1652 insertions(+) create mode 100644 pkg/services/dns/hosts_file.go create mode 100644 pkg/services/dns/hosts_file_test.go create mode 100644 vendor/github.com/areYouLazy/libhosty/.gitignore create mode 100644 vendor/github.com/areYouLazy/libhosty/LICENSE create mode 100644 vendor/github.com/areYouLazy/libhosty/README.md create mode 100644 vendor/github.com/areYouLazy/libhosty/TODO.md create mode 100644 vendor/github.com/areYouLazy/libhosty/coverage_badge.png create mode 100644 vendor/github.com/areYouLazy/libhosty/errors.go create mode 100644 vendor/github.com/areYouLazy/libhosty/formatter.go create mode 100644 vendor/github.com/areYouLazy/libhosty/helper.go create mode 100644 vendor/github.com/areYouLazy/libhosty/libhosty.go create mode 100644 vendor/github.com/areYouLazy/libhosty/parser.go create mode 100644 vendor/github.com/areYouLazy/libhosty/templates.go diff --git a/go.mod b/go.mod index 78d987f43..8ab996969 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22 require ( github.com/Microsoft/go-winio v0.6.2 github.com/apparentlymart/go-cidr v1.1.0 + github.com/areYouLazy/libhosty v1.1.0 github.com/containers/winquit v1.1.0 github.com/coreos/stream-metadata-go v0.4.4 github.com/dustin/go-humanize v1.0.1 diff --git a/go.sum b/go.sum index fbf137c80..b8373e0f4 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/areYouLazy/libhosty v1.1.0 h1:kO6UTk9z72cHW28A/V1kKi7C8iKQGqINiVGXp+05Eao= +github.com/areYouLazy/libhosty v1.1.0/go.mod h1:dV4ir3feRrTbWdcJ21mt3MeZlASg0sc8db6nimL9GOA= github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE= github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8= diff --git a/pkg/services/dns/dns.go b/pkg/services/dns/dns.go index a25a8dcd8..018c0036e 100644 --- a/pkg/services/dns/dns.go +++ b/pkg/services/dns/dns.go @@ -2,12 +2,14 @@ package dns import ( "encoding/json" + "errors" "fmt" "net" "net/http" "strings" "sync" + "github.com/areYouLazy/libhosty" "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/miekg/dns" log "github.com/sirupsen/logrus" @@ -18,6 +20,7 @@ type dnsHandler struct { zonesLock sync.RWMutex udpClient *dns.Client tcpClient *dns.Client + hostsFile HostsFile nameserver string } @@ -28,11 +31,17 @@ func newDNSHandler(zones []types.Zone) (*dnsHandler, error) { return nil, err } + hostsFile, err := NewHostsFile("") + if err != nil { + return nil, err + } + return &dnsHandler{ zones: zones, tcpClient: &dns.Client{Net: "tcp"}, udpClient: &dns.Client{Net: "udp"}, nameserver: net.JoinHostPort(nameserver, port), + hostsFile: hostsFile, }, nil } @@ -98,6 +107,24 @@ func (h *dnsHandler) addLocalAnswers(m *dns.Msg, q dns.Question) bool { m.Rcode = dns.RcodeNameError return true } + ip, err := h.hostsFile.LookupByHostname(q.Name) + if err != nil { + // ignore only ErrHostnameNotFound error + if !errors.Is(err, libhosty.ErrHostnameNotFound) { + log.Errorf("Error during looking in hosts file records: %v", err) + } + } else { + m.Answer = append(m.Answer, &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 0, + }, + A: ip, + }) + return true + } } return false } @@ -106,6 +133,7 @@ func (h *dnsHandler) addAnswers(dnsClient *dns.Client, r *dns.Msg) *dns.Msg { m := new(dns.Msg) m.SetReply(r) m.RecursionAvailable = true + for _, q := range m.Question { if done := h.addLocalAnswers(m, q); done { return m diff --git a/pkg/services/dns/hosts_file.go b/pkg/services/dns/hosts_file.go new file mode 100644 index 000000000..bfa51844c --- /dev/null +++ b/pkg/services/dns/hosts_file.go @@ -0,0 +1,94 @@ +package dns + +import ( + "net" + "sync" + + "github.com/areYouLazy/libhosty" + "github.com/fsnotify/fsnotify" + log "github.com/sirupsen/logrus" +) + +type HostsFile interface { + LookupByHostname(name string) (net.IP, error) +} + +type hosts struct { + hostsReadLock sync.RWMutex + hostsFilePath string + hostsFile *libhosty.HostsFile +} + +// NewHostsFile Creates new HostsFile instance +// Pass ""(empty string) if you want to use default hosts file +func NewHostsFile(hostsPath string) (HostsFile, error) { + hostsFile, err := readHostsFile(hostsPath) + if err != nil { + return nil, err + } + watcher, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + + h := &hosts{ + hostsFile: hostsFile, + hostsFilePath: hostsFile.Config.FilePath, + } + go func() { + h.startWatch(watcher) + }() + return h, nil +} + +func (h *hosts) startWatch(w *fsnotify.Watcher) { + err := w.Add(h.hostsFilePath) + if err != nil { + log.Errorf("Hosts file adding watcher error:%s", err) + return + } + for { + select { + case err, ok := <-w.Errors: + if !ok { + return + } + log.Errorf("Hosts file watcher error:%s", err) + case event, ok := <-w.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write { + err := h.updateHostsFile() + if err != nil { + log.Errorf("Hosts file read error:%s", err) + return + } + } + } + } +} + +func (h *hosts) LookupByHostname(name string) (net.IP, error) { + _, ip, err := h.hostsFile.LookupByHostname(name) + return ip, err +} + +func (h *hosts) updateHostsFile() error { + h.hostsReadLock.RLock() + defer h.hostsReadLock.RUnlock() + newHosts, err := readHostsFile(h.hostsFilePath) + if err != nil { + return err + } + h.hostsFile = newHosts + return nil +} + +func readHostsFile(hostsFilePath string) (*libhosty.HostsFile, error) { + config, err := libhosty.NewHostsFileConfig(hostsFilePath) + if err != nil { + return nil, err + } + return libhosty.InitWithConfig(config) +} diff --git a/pkg/services/dns/hosts_file_test.go b/pkg/services/dns/hosts_file_test.go new file mode 100644 index 000000000..4d2f69916 --- /dev/null +++ b/pkg/services/dns/hosts_file_test.go @@ -0,0 +1,41 @@ +package dns + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestHostsFile(t *testing.T) { + hostsFile := filepath.Join(t.TempDir(), "hosts") + assert.NoError(t, os.WriteFile(hostsFile, []byte(`127.0.0.1 entry1`), 0600)) + + hosts, err := NewHostsFile(hostsFile) + assert.NoError(t, err) + ip, err := hosts.LookupByHostname("entry1") + assert.NoError(t, err) + assert.Equal(t, "127.0.0.1", ip.String()) +} + +func TestReloadingHostsFile(t *testing.T) { + hostsFile := filepath.Join(t.TempDir(), "hosts") + assert.NoError(t, os.WriteFile(hostsFile, []byte(`127.0.0.1 entry1`), 0600)) + + hosts, err := NewHostsFile(hostsFile) + time.Sleep(time.Second) + assert.NoError(t, err) + ip, err := hosts.LookupByHostname("entry1") + assert.NoError(t, err) + assert.Equal(t, "127.0.0.1", ip.String()) + + assert.NoError(t, os.WriteFile(hostsFile, []byte(`127.0.0.1 entry2 foobar`), 0600)) + time.Sleep(time.Second) + + ipBar, err := hosts.LookupByHostname("foobar") + assert.NoError(t, err) + assert.Equal(t, "127.0.0.1", ipBar.String()) + +} diff --git a/vendor/github.com/areYouLazy/libhosty/.gitignore b/vendor/github.com/areYouLazy/libhosty/.gitignore new file mode 100644 index 000000000..d99b58751 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/.gitignore @@ -0,0 +1,5 @@ +# test coverage output +./coverage.out +./cover.out +coverage.out +cover.out diff --git a/vendor/github.com/areYouLazy/libhosty/LICENSE b/vendor/github.com/areYouLazy/libhosty/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/areYouLazy/libhosty/README.md b/vendor/github.com/areYouLazy/libhosty/README.md new file mode 100644 index 000000000..e87c798b3 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/README.md @@ -0,0 +1,167 @@ +# libhosty + +[![made-with-Go](https://img.shields.io/badge/made%20with-Go-1f425f.svg)](http://golang.org) +[![Go Report Card](https://goreportcard.com/badge/github.com/areYouLazy/libhosty)](https://goreportcard.com/report/github.com/areYouLazy/libhosty) +[![Build and Test](https://github.com/areYouLazy/libhosty/actions/workflows/build-and-test.yml/badge.svg?branch=main&event=push)](https://github.com/areYouLazy/libhosty/actions/workflows/build-and-test.yml) +![gopherbadger-tag-do-not-edit](coverage_badge.png) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/areYouLazy/libhosty) + +## Description + +libhosty is a pure golang library to manipulate the hosts file. It is inspired by [txeh](https://github.com/txn2/txeh), with some enrichments. + +## Table of Contents + +* [Main Features](#Main-Features) +* [Installation](#Installation) +* [Usage](#Usage) +* [Contributing](#Contributing) + * [Templates](#Templates) +* [Credits](#Credits) +* [License](#License) + +## Main Features + +* Comment/Uncomment a line without removing it from the file +* Restore the default hosts file for your system +* Add/Remove Address lines +* Add/Remove Comment lines +* Add/Remove Empty lines +* Query by hostname +* Automatically handles duplicate entries + +## Installation + +Ensure you have go on your system + +```bash +> go version +go version go1.15.6 linux/amd64 +``` + +and pull the library + +```bash +> go get github.com/areYouLazy/libhosty +``` + +## Usage + +To use the library, just import it and call the `Init()` method. + +Note: This code doesn't handle errors for readability purposes, but you SHOULD! + +```go +package main + +import "github.com/areYouLazy/libhosty" + +func main() { + //you can define a custom config object + // and use it to initialize libhosty with a custom hosts file + // + //cnf, _ := libhosty.NewHostsFileConfig("/home/sonica/hosts-export.txt") + //hfl, _ := libhosty.InitWithConfig(cnf) + + //or initialize libhosty that will automatically try to loads + // then default hosts file for your OS + hfl, _ := libhosty.Init() + + //add an empty line + hfl.AddEmptyFileLine() + + //add a host with a comment + hfl.AddHostFileLine("12.12.12.12", "my.host.name", "comment on my hostname!") + + //add a comment + hfl.AddCommentFileLine("just a comment") + + //add an empty line + hfl.AddEmptyFileLine() + + //add another host without comment + hfl.AddHostsFileLine("13.13.13.13", "another.host.name", "") + + //add another fqdn to the previous ip + hfl.AddHostsFileLine("12.12.12.12", "second.host.name", "") + + // comment for host lines can be done by hostname, row line + // or IP (as net.IP or string) + // + // Comment the line with address 12.12.12.12 + // + // By-Row-Number + idx, _ := hfl.GetHostsFileLineByHostname("second.host.name") + hfl.CommentHostsFileLineByRow(idx) + // + // By-Hostname + hfl.CommentHostsFileLineByHostname("second.host.name") + // + // By-Address-As-IP + ip := net.ParseIP("12.12.12.12") + hfl.CommentHostsFileLineByIP(ip) + // + // By-Address-As-String + hfl.CommentHostsFileLineByAddress("12.12.12.12") + + // render the hosts file + fmt.Println(hfl.RenderHostsFile()) + + // write file to disk + hfl.SaveHostsFile() + + // or to a custom location + hfl.SaveHostsFileAs("/home/sonica/hosts-export.txt") + + // restore the original hosts file for linux + hfl.RestoreDefaultLinuxHostsFile() + + // render the hosts file + fmt.Println(hfl.RenderHostsFile()) + + // write to disk + hfl.SaveHostsFile() +} +``` + +The 1st `fmt.Println()` should output something like this (in a linux host) + +```console +# Do not remove the following line, or various programs +# that require network functionality will fail. +127.0.0.1 localhost.localdomain localhost +::1 localhost6.localdomain6 localhost6 + +# 12.12.12.12 my.host.name second.host.name #comment on my hostname! +# just a comment line + +13.13.13.13 another.host.name +``` + +While the 2nd `fmt.Println()` should output the default template for linux systems + +```console +# Do not remove the following line, or various programs +# that require network functionality will fail. +127.0.0.1 localhost.localdomain localhost +::1 localhost6.localdomain6 localhost6 + +``` + +If you handle errors properly, you'll notice that this example program will fail on the `SaveHostsFile()` call if started as a normal user, as editing the hosts file requires root privileges. This does not prevent libhosty from loading, managing, rendering and exporting the hosts file + +## Contributing + +Issues and PRs are more than welcome! + +### Templates + +If you find a hosts template (like the Docker one) that you think can be useful to have in this library feel free to open a Pull Request + +## Credits + +Project Contributors will be listed here + +## License + +Licenses under Apache License 2.0 diff --git a/vendor/github.com/areYouLazy/libhosty/TODO.md b/vendor/github.com/areYouLazy/libhosty/TODO.md new file mode 100644 index 000000000..136bb9f49 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/TODO.md @@ -0,0 +1,4 @@ +# TODO + +* Improve tests +* Improve comments diff --git a/vendor/github.com/areYouLazy/libhosty/coverage_badge.png b/vendor/github.com/areYouLazy/libhosty/coverage_badge.png new file mode 100644 index 0000000000000000000000000000000000000000..14cc422ee89fab91ac12e51487d025a1e7066146 GIT binary patch literal 2202 zcmV;L2xa$)P)Px-R7pfZRA_|qzpZDkUdB30U_xs%Qob>Fo&ra29wFf0hS`JuvfO|$y@t-F& z{{W&>LVVWS?=DqTRXqf(6GBXAQTH<@T003WAg!OGL?{Z7G0_Fx@0cw5NfH@~5W+ao zh1~C$EQTU5DwC#|`xui+!v}``A269T9Me!)mdVP>A}uYAsHiA9J3Bdd?i?R}_#s}e z_ns2TvP^b%Hfd>Tn9XLcU%yUORTVWgHUDc56OPd|R3t4$v$m0L$s$5AbJbN(>6e)R zyg9E5t04t|(l0Z)>Z%XM$&$jWbIO@#Ucwh$A9L{Qp95f2B6&5roL^kYB_RAEYZ=qf zO`A4hu~^u#V+YO6%~&iJGBPq)uwVhTwYB%u^bptf?b~T>Z6z))4u``*eSJM{xBH&B zCL2c6@MrNa5vNIF!|xvF+ReYCL}-Xg(E;E%`WSxq03U4nEB@O9BjO)1FT?qrn~Zno z@aw0JGRLrhCf5ZDFDVAUR>SX-R#-32cyx*!i5V*<;ls(!C)|O?AS2? z3Q=KYpA`mo5QNQBlG1dijv)MxC7|T$tW(upP7O>&uGJ50N zup~_xF)uPjJ2+7qyf!@E=N^DCLDYN|A;5QgfO)1RxVw94)?P%5X3Q;KGemgjm_B_v zZEbA?0s&N2MUo_BS;k;65Ed4O(P(7z=FJ>BbciRPe3HwTFH>Ax{JpspMZs(~6B!v9 zlBcSw3cKBoBuNM%SiE>KpMU;28jXg+!b0}$-OCeCJi)GAyC^CuLRHmZvs$f|yu3W# zdFLI35M*X%l9rZ6W@aW04GpYayB3qlL{U)@ufP5}xw*L*jmBWjdcA(k9Ak|isd0Gv z`^d59^ZUnI`R${%EPW{Ru6aWy4|gLgz?Vj{bD?*^c{x|;Ln&(ql0NK;c25fKr*_uhLv^UO2obUHj9&j_wS zATWY!IILD{NFKM_%{SkCLuzU&7cN{NJw2VnhYypKl*G)LGbt}GAHij@Sh#lW8UQCw zoFF11B6u^Dl$5Z1`Etx=Ga8MCy1F_N5)!Dducx)OmGJOzjvYJ3>eZ_eLSVDm#?3K= zJ9`rS8jMrS^z^t`{$?VnGk(H>tarK6bB-==`>=^1#K?HbPR{1$<`G=OF&2+Tqv6b%Go+`d)6vmESXdY>EiFVvMRD=sMM_Fa z?h;#7RbcSu84}xOvthT}dH(t5X>V`GZnv|3{rX@It#)t=)a&)Z7{A{?ZjPWYcl6uQ zdkul$)YnJRr}$`a)zEh9A52ve==5B_L+O!QE)p2$4D!e8lZcZODRb?j?9q?edNLPT zBlE(_U0nJu>rTrC`wq2qZ``;+b#*nRrKMP{R>H%>v0AOnm@xw(1dWZ2Bqt}6nwpAE zr{n3TpXTb-tMv8tjmmZQ>{&`nOPM`;Hma%;7Z*oyaWUV1`|X%K=g*&K?%cVoS+j=I zr%$7*DvgbeBqSt|pPx^Bd_1wSvBbv4q9{sGoS~H|gg}xcd_Esqt(K&uBsOl`2tY$a z11TvfBqt{aa}OCbUXHQGZK;PZTK>qo`32~dFc!^RMyzQ%EnQ9HZn?w9rrA3d_EtEiHU64 zvIUF9!dG8?#lC&}LjIg_xm;}6uz{5;S7J7sX=`hvqN0M@+FFv5l0xzbAt)>?WYwxw zY}~k!zP>&N1_rQLENtDn6|2=6+~)K1^8tAO{r6e7Ze1{Ue}6wEB_%9fx|FW2E^2CO zu-RCJqQ>RXmot@2d&pkJ8j^P;X<-Gn{EIU_}G1u}4|LXlG2haS9 zOW}V&)=Iqc88Y+aq?RE!6)Mzy5bUF+M1F|gR^?GqSoxz74lgWglDBp`qQ53?%!wCxu z8<(f2r-$C&ULJnm`LG~gAuoBjCQ0$B(8849|n!DGPH?8n;~ z80Lb+Lw4WD*r+TPJpcdz07*qoM6N<$f-#*n-2eap literal 0 HcmV?d00001 diff --git a/vendor/github.com/areYouLazy/libhosty/errors.go b/vendor/github.com/areYouLazy/libhosty/errors.go new file mode 100644 index 000000000..d4fae1a1d --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/errors.go @@ -0,0 +1,38 @@ +package libhosty + +import ( + "errors" + "fmt" +) + +//ErrNotAnAddressLine used when operating on a non-address line for operation +// related to address lines, such as comment/uncomment +var ErrNotAnAddressLine = errors.New("this line is not of type ADDRESS") + +//ErrUncommentableLine used when try to comment a line that cannot be commented +var ErrUncommentableLine = errors.New("this line cannot be commented") + +//ErrAlredyCommentedLine used when try to comment an alredy commented line +var ErrAlredyCommentedLine = errors.New("this line is alredy commented") + +//ErrAlredyUncommentedLine used when try to uncomment an alredy uncommented line +var ErrAlredyUncommentedLine = errors.New("this line is alredy uncommented") + +//ErrAddressNotFound used when provided address is not found +var ErrAddressNotFound = errors.New("cannot find a line with given address") + +//ErrHostnameNotFound used when provided hostname is not found +var ErrHostnameNotFound = errors.New("cannot find a line with given hostname") + +//ErrUnknown used when we don't know what's happened +var ErrUnknown = errors.New("unknown error") + +//ErrCannotParseIPAddress used when unable to parse given ip address +func ErrCannotParseIPAddress(ip string) error { + return fmt.Errorf("cannot parse IP Address: %s", ip) +} + +//ErrUnrecognizedOS used when unable to recognize OS +func ErrUnrecognizedOS(os string) error { + return fmt.Errorf("unrecognized OS: %s", os) +} diff --git a/vendor/github.com/areYouLazy/libhosty/formatter.go b/vendor/github.com/areYouLazy/libhosty/formatter.go new file mode 100644 index 000000000..994e5bd31 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/formatter.go @@ -0,0 +1,33 @@ +package libhosty + +import ( + "fmt" + "strings" +) + +// lineFormatter return a readable form for the given HostsFileLine object +func lineFormatter(hfl HostsFileLine) string { + + // returns raw if we don't need to edit the line + // this is for UNKNOWN, EMPTY and COMMENT linetypes + if hfl.Type < LineTypeAddress { + return hfl.Raw + } + + // check if it's a commented line + if hfl.IsCommented { + // check if there's a comment for that line + if len(hfl.Comment) > 0 { + return fmt.Sprintf("# %-16s %s #%s", hfl.Address, strings.Join(hfl.Hostnames, " "), hfl.Comment) + } + + return fmt.Sprintf("# %-16s %s", hfl.Address, strings.Join(hfl.Hostnames, " ")) + } + + // return the actual hosts entry + if len(hfl.Comment) > 0 { + return fmt.Sprintf("%-16s %s #%s", hfl.Address, strings.Join(hfl.Hostnames, " "), hfl.Comment) + } + + return fmt.Sprintf("%-16s %s", hfl.Address, strings.Join(hfl.Hostnames, " ")) +} diff --git a/vendor/github.com/areYouLazy/libhosty/helper.go b/vendor/github.com/areYouLazy/libhosty/helper.go new file mode 100644 index 000000000..705916af4 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/helper.go @@ -0,0 +1,25 @@ +package libhosty + +//RestoreDefaultWindowsHostsFile loads the default windows hosts file +func (h *HostsFile) RestoreDefaultWindowsHostsFile() { + hfl, _ := ParseHostsFileAsString(windowsHostsTemplate) + h.HostsFileLines = hfl +} + +//RestoreDefaultLinuxHostsFile loads the default linux hosts file +func (h *HostsFile) RestoreDefaultLinuxHostsFile() { + hfl, _ := ParseHostsFileAsString(linuxHostsTemplate) + h.HostsFileLines = hfl +} + +//RestoreDefaultDarwinHostsFile loads the default darwin hosts file +func (h *HostsFile) RestoreDefaultDarwinHostsFile() { + hfl, _ := ParseHostsFileAsString(darwinHostsTemplate) + h.HostsFileLines = hfl +} + +//AddDockerDesktopTemplate adds the dockerDesktopTemplate to the actual hostsFile +func (h *HostsFile) AddDockerDesktopTemplate() { + hfl, _ := ParseHostsFileAsString(dockerDesktopTemplate) + h.HostsFileLines = append(h.HostsFileLines, hfl...) +} diff --git a/vendor/github.com/areYouLazy/libhosty/libhosty.go b/vendor/github.com/areYouLazy/libhosty/libhosty.go new file mode 100644 index 000000000..3504e8b84 --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/libhosty.go @@ -0,0 +1,846 @@ +//Package libhosty is a pure golang library to manipulate the hosts file +package libhosty + +import ( + "io/ioutil" + "net" + "os" + "regexp" + "runtime" + "strings" + "sync" +) + +const ( + //Version exposes library version + Version = "2.0" +) + +const ( + // defines default path for windows os + windowsFilePath = "C:\\Windows\\System32\\drivers\\etc\\" + + // defines default path for linux os + unixFilePath = "/etc/" + + // defines default filename + hostsFileName = "hosts" +) + +//LineType define a safe type for line type enumeration +type LineType int + +const ( + //LineTypeUnknown defines unknown lines + LineTypeUnknown LineType = 0 + + //LineTypeEmpty defines empty lines + LineTypeEmpty LineType = 10 + + //LineTypeComment defines comment lines (starts with #) + LineTypeComment LineType = 20 + + //LineTypeAddress defines address lines (actual hosts lines) + LineTypeAddress LineType = 30 +) + +//HostsFileConfig defines parameters to find hosts file. +// FilePath is the absolute path of the hosts file (filename included) +type HostsFileConfig struct { + FilePath string +} + +//HostsFileLine holds hosts file lines data +type HostsFileLine struct { + //Number is the original line number + Number int + + //LineType defines the line type + Type LineType + + //Address is a net.IP representation of the address + Address net.IP + + //Parts is a slice of the line splitted by '#' + Parts []string + + //Hostnames is a slice of hostnames for the relative IP + Hostnames []string + + //Raw is the raw representation of the line, as it is in the hosts file + Raw string + + //Comment is the comment part of the line (if present in an ADDRESS line) + Comment string + + //IsCommented to know if the current ADDRESS line is commented out (starts with '#') + IsCommented bool + + //trimed is a trimed version (no spaces before and after) of the line + trimed string +} + +//HostsFile is a reference for the hosts file configuration and lines +type HostsFile struct { + sync.Mutex + + //Config reference to a HostsConfig object + Config *HostsFileConfig + + //HostsFileLines slice of HostsFileLine objects + HostsFileLines []HostsFileLine +} + +//InitWithConfig returns a new instance of a hostsfile. +// InitWithConfig is meant to be used with a custom conf file +// however InitWithConfig() will fallback to Init() if conf is nill +// You should use Init() to load hosts file from default location +func InitWithConfig(conf *HostsFileConfig) (*HostsFile, error) { + var config *HostsFileConfig + var err error + + if conf != nil { + config = conf + } else { + return Init() + } + + // allocate a new HostsFile object + hf := &HostsFile{ + // use default configuration + Config: config, + + // allocate a new slice of HostsFileLine objects + HostsFileLines: make([]HostsFileLine, 0), + } + + // parse the hosts file and load file lines + hf.HostsFileLines, err = ParseHostsFile(hf.Config.FilePath) + if err != nil { + return nil, err + } + + //return HostsFile + return hf, nil +} + +//Init returns a new instance of a hostsfile. +func Init() (*HostsFile, error) { + // initialize hostsConfig + config, err := NewHostsFileConfig("") + if err != nil { + return nil, err + } + + // allocate a new HostsFile object + hf := &HostsFile{ + // use default configuration + Config: config, + + // allocate a new slice of HostsFileLine objects + HostsFileLines: make([]HostsFileLine, 0), + } + + // parse the hosts file and load file lines + hf.HostsFileLines, err = ParseHostsFile(hf.Config.FilePath) + if err != nil { + return nil, err + } + + //return HostsFile + return hf, nil +} + +//NewHostsFileConfig loads hosts file based on environment. +// NewHostsFileConfig initialize the default file path based +// on the OS or from a given location if a custom path is provided +func NewHostsFileConfig(path string) (*HostsFileConfig, error) { + // allocate hostsConfig + var hc *HostsFileConfig + + // ensure custom path exists + // https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go + if fh, err := os.Stat(path); err == nil { + // eusure custom path points to a file (not a directory) + if !fh.IsDir() { + hc = &HostsFileConfig{ + FilePath: path, + } + } + } else { + // check os to construct default path + switch runtime.GOOS { + case "windows": + hc = &HostsFileConfig{ + FilePath: windowsFilePath + hostsFileName, + } + default: + hc = &HostsFileConfig{ + FilePath: unixFilePath + hostsFileName, + } + } + } + + return hc, nil +} + +//GetHostsFileLines returns every address row +func (h *HostsFile) GetHostsFileLines() []*HostsFileLine { + var hfl []*HostsFileLine + + for idx := range h.HostsFileLines { + if h.HostsFileLines[idx].Type == LineTypeAddress { + hfl = append(hfl, h.GetHostsFileLineByRow(idx)) + } + } + + return hfl +} + +//GetHostsFileLineByRow returns a ponter to the given HostsFileLine row +func (h *HostsFile) GetHostsFileLineByRow(row int) *HostsFileLine { + return &h.HostsFileLines[row] +} + +//GetHostsFileLineByIP returns the index of the line and a ponter to the given HostsFileLine line +func (h *HostsFile) GetHostsFileLineByIP(ip net.IP) (int, *HostsFileLine) { + if ip == nil { + return -1, nil + } + + for idx := range h.HostsFileLines { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + return idx, &h.HostsFileLines[idx] + } + } + + return -1, nil +} + +func (h *HostsFile) GetHostsFileLinesByIP(ip net.IP) []*HostsFileLine { + if ip == nil { + return nil + } + + hfl := make([]*HostsFileLine, 0) + + for idx := range h.HostsFileLines { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + hfl = append(hfl, &h.HostsFileLines[idx]) + } + } + + return hfl +} + +//GetHostsFileLineByAddress returns the index of the line and a ponter to the given HostsFileLine line +func (h *HostsFile) GetHostsFileLineByAddress(address string) (int, *HostsFileLine) { + ip := net.ParseIP(address) + return h.GetHostsFileLineByIP(ip) +} + +func (h *HostsFile) GetHostsFileLinesByAddress(address string) []*HostsFileLine { + ip := net.ParseIP(address) + return h.GetHostsFileLinesByIP(ip) +} + +//GetHostsFileLineByHostname returns the index of the line and a ponter to the given HostsFileLine line +func (h *HostsFile) GetHostsFileLineByHostname(hostname string) (int, *HostsFileLine) { + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + return idx, &h.HostsFileLines[idx] + } + } + } + + return -1, nil +} + +func (h *HostsFile) GetHostsFileLinesByHostname(hostname string) []*HostsFileLine { + hfl := make([]*HostsFileLine, 0) + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + hfl = append(hfl, &h.HostsFileLines[idx]) + continue + } + } + } + + return hfl +} + +func (h *HostsFile) GetHostsFileLinesByHostnameAsRegexp(hostname string) []*HostsFileLine { + hfl := make([]*HostsFileLine, 0) + + reg := regexp.MustCompile(hostname) + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if reg.MatchString(hn) { + hfl = append(hfl, &h.HostsFileLines[idx]) + continue + } + } + } + + return hfl +} + +//RenderHostsFile render and returns the hosts file with the lineFormatter() routine +func (h *HostsFile) RenderHostsFile() string { + // allocate a buffer for file lines + var sliceBuffer []string + + // iterate HostsFileLines and popolate the buffer with formatted lines + for _, l := range h.HostsFileLines { + sliceBuffer = append(sliceBuffer, lineFormatter(l)) + } + + // strings.Join() prevent the last line from being a new blank line + // as opposite to a for loop with fmt.Printf(buffer + '\n') + return strings.Join(sliceBuffer, "\n") +} + +//RenderHostsFileLine render and returns the given hosts line with the lineFormatter() routine +func (h *HostsFile) RenderHostsFileLine(row int) string { + // iterate to find the row to render + if len(h.HostsFileLines) > row { + return lineFormatter(h.HostsFileLines[row]) + } + + return "" +} + +//SaveHostsFile write hosts file to configured path. +// error is not nil if something goes wrong +func (h *HostsFile) SaveHostsFile() error { + return h.SaveHostsFileAs(h.Config.FilePath) +} + +//SaveHostsFileAs write hosts file to the given path. +// error is not nil if something goes wrong +func (h *HostsFile) SaveHostsFileAs(path string) error { + // render the file as a byte slice + dataBytes := []byte(h.RenderHostsFile()) + + // write file to disk + err := ioutil.WriteFile(path, dataBytes, 0644) + if err != nil { + return err + } + + return nil +} + +//RemoveHostsFileLineByRow remove row at given index from HostsFileLines +func (h *HostsFile) RemoveHostsFileLineByRow(row int) { + // prevent out-of-index + if row < len(h.HostsFileLines) { + h.Lock() + h.HostsFileLines = append(h.HostsFileLines[:row], h.HostsFileLines[row+1:]...) + h.Unlock() + } +} + +func (h *HostsFile) RemoveHostsFileLineByIP(ip net.IP) { + for idx := len(h.HostsFileLines) - 1; idx >= 0; idx-- { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + h.RemoveHostsFileLineByRow(idx) + return + } + } +} + +func (h *HostsFile) RemoveHostsFileLinesByIP(ip net.IP) { + for idx := len(h.HostsFileLines) - 1; idx >= 0; idx-- { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + h.RemoveHostsFileLineByRow(idx) + } + } +} + +func (h *HostsFile) RemoveHostsFileLineByAddress(address string) { + ip := net.ParseIP(address) + + h.RemoveHostsFileLineByIP(ip) +} + +func (h *HostsFile) RemoveHostsFileLinesByAddress(address string) { + ip := net.ParseIP(address) + + h.RemoveHostsFileLinesByIP(ip) +} + +func (h *HostsFile) RemoveHostsFileLineByHostname(hostname string) { + for idx := len(h.HostsFileLines) - 1; idx >= 0; idx-- { + if h.HostsFileLines[idx].Type == LineTypeAddress { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + h.RemoveHostsFileLineByRow(idx) + return + } + } + } + } +} + +func (h *HostsFile) RemoveHostsFileLinesByHostnameAsRegexp(hostname string) { + reg := regexp.MustCompile(hostname) + + for idx := len(h.HostsFileLines) - 1; idx >= 0; idx-- { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if reg.MatchString(hn) { + h.RemoveHostsFileLineByRow(idx) + continue + } + } + } +} + +func (h *HostsFile) RemoveHostsFileLinesByHostname(hostname string) { + for idx := len(h.HostsFileLines) - 1; idx >= 0; idx-- { + if h.HostsFileLines[idx].Type == LineTypeAddress { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + h.RemoveHostsFileLineByRow(idx) + continue + } + } + } + } +} + +//LookupByHostname check if the given fqdn exists. +// if yes, it returns the index of the address and the associated address. +// error is not nil if something goes wrong +func (h *HostsFile) LookupByHostname(hostname string) (int, net.IP, error) { + for idx, hfl := range h.HostsFileLines { + for _, hn := range hfl.Hostnames { + if hn == hostname { + return idx, h.HostsFileLines[idx].Address, nil + } + } + } + + return -1, nil, ErrHostnameNotFound +} + +//AddHostsFileLineRaw add the given ip/fqdn/comment pair +// this is different from AddHostFileLine because it does not take care of duplicates +// this just append the new entry to the hosts file +func (h *HostsFile) AddHostsFileLineRaw(ipRaw, fqdnRaw, comment string) (int, *HostsFileLine, error) { + // hostname to lowercase + hostname := strings.ToLower(fqdnRaw) + // parse ip to net.IP + ip := net.ParseIP(ipRaw) + + // if we have a valid IP + if ip != nil { + // create a new hosts line + hfl := HostsFileLine{ + Type: LineTypeAddress, + Address: ip, + Hostnames: []string{hostname}, + Comment: comment, + IsCommented: false, + } + + // append to hosts + h.HostsFileLines = append(h.HostsFileLines, hfl) + + // get index + idx := len(h.HostsFileLines) - 1 + + // return created entry + return idx, &h.HostsFileLines[idx], nil + } + + // return error + return -1, nil, ErrCannotParseIPAddress(ipRaw) +} + +//AddHostsFileLine add the given ip/fqdn/comment pair, cleanup is done for previous entry. +// it returns the index of the edited (created) line and a pointer to the hostsfileline object. +// error is not nil if something goes wrong +func (h *HostsFile) AddHostsFileLine(ipRaw, fqdnRaw, comment string) (int, *HostsFileLine, error) { + // hostname to lowercase + hostname := strings.ToLower(fqdnRaw) + // parse ip to net.IP + ip := net.ParseIP(ipRaw) + + // if we have a valid IP + if ip != nil { + //check if we alredy have the fqdn + if idx, addr, err := h.LookupByHostname(hostname); err == nil { + //if actual ip is the same as the given one, we are done + if net.IP.Equal(addr, ip) { + // handle comment + if comment != "" { + // just replace the current comment with the new one + h.HostsFileLines[idx].Comment = comment + } + return idx, &h.HostsFileLines[idx], nil + } + + //if address is different, we need to remove the hostname from the previous entry + for hostIdx, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + if len(h.HostsFileLines[idx].Hostnames) > 1 { + h.Lock() + h.HostsFileLines[idx].Hostnames = append(h.HostsFileLines[idx].Hostnames[:hostIdx], h.HostsFileLines[idx].Hostnames[hostIdx+1:]...) + h.Unlock() + } + + //remove the line if there are no more hostnames (other than the actual one) + if len(h.HostsFileLines[idx].Hostnames) <= 1 { + h.RemoveHostsFileLineByRow(idx) + } + } + } + } + + //if we alredy have the address, just add the hostname to that line + for idx, hfl := range h.HostsFileLines { + if net.IP.Equal(hfl.Address, ip) { + h.Lock() + h.HostsFileLines[idx].Hostnames = append(h.HostsFileLines[idx].Hostnames, hostname) + h.Unlock() + + // handle comment + if comment != "" { + // just replace the current comment with the new one + h.HostsFileLines[idx].Comment = comment + } + + // return edited entry + return idx, &h.HostsFileLines[idx], nil + } + } + + // at this point we need to create new host line + hfl := HostsFileLine{ + Type: LineTypeAddress, + Address: ip, + Hostnames: []string{hostname}, + Comment: comment, + IsCommented: false, + } + + // generate raw version of the line + hfl.Raw = lineFormatter(hfl) + + // append to hosts + h.HostsFileLines = append(h.HostsFileLines, hfl) + + // get index + idx := len(h.HostsFileLines) - 1 + + // return created entry + return idx, &h.HostsFileLines[idx], nil + } + + // return error + return -1, nil, ErrCannotParseIPAddress(ipRaw) +} + +//AddCommentFileLine adds a new line of type comment with the given comment. +// it returns the index of the edited (created) line and a pointer to the hostsfileline object. +// error is not nil if something goes wrong +func (h *HostsFile) AddCommentFileLine(comment string) (int, *HostsFileLine, error) { + h.Lock() + defer h.Unlock() + + hfl := HostsFileLine{ + Type: LineTypeComment, + Raw: "# " + comment, + Comment: comment, + } + + hfl.Raw = lineFormatter(hfl) + + h.HostsFileLines = append(h.HostsFileLines, hfl) + idx := len(h.HostsFileLines) - 1 + return idx, &h.HostsFileLines[idx], nil +} + +//AddEmptyFileLine adds a new line of type empty. +// it returns the index of the edited (created) line and a pointer to the hostsfileline object. +// error is not nil if something goes wrong +func (h *HostsFile) AddEmptyFileLine() (int, *HostsFileLine, error) { + h.Lock() + defer h.Unlock() + + hfl := HostsFileLine{ + Type: LineTypeEmpty, + Raw: "", + } + + h.HostsFileLines = append(h.HostsFileLines, hfl) + idx := len(h.HostsFileLines) - 1 + return idx, &h.HostsFileLines[idx], nil +} + +//CommentHostsFileLineByRow set the IsCommented bit for the given row to true +func (h *HostsFile) CommentHostsFileLineByRow(row int) error { + h.Lock() + defer h.Unlock() + + if len(h.HostsFileLines) > row { + if h.HostsFileLines[row].Type == LineTypeAddress { + if !h.HostsFileLines[row].IsCommented { + h.HostsFileLines[row].IsCommented = true + + h.HostsFileLines[row].Raw = h.RenderHostsFileLine(row) + return nil + } + + return ErrAlredyCommentedLine + } + + return ErrNotAnAddressLine + } + + return ErrUnknown +} + +//CommentHostsFileLineByIP set the IsCommented bit for the given address to true +func (h *HostsFile) CommentHostsFileLineByIP(ip net.IP) error { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + if !h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = true + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + return nil + } + + return ErrAlredyCommentedLine + } + } + + return ErrAddressNotFound +} + +func (h *HostsFile) CommentHostsFileLinesByIP(ip net.IP) { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + if !h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = true + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + } + } + } +} + +//CommentHostsFileLineByAddress set the IsCommented bit for the given address as string to true +func (h *HostsFile) CommentHostsFileLineByAddress(address string) error { + ip := net.ParseIP(address) + + return h.CommentHostsFileLineByIP(ip) +} + +func (h *HostsFile) CommentHostsFileLinesByAddress(address string) { + ip := net.ParseIP(address) + h.CommentHostsFileLinesByIP(ip) +} + +//CommentHostsFileLineByHostname set the IsCommented bit for the given hostname to true +func (h *HostsFile) CommentHostsFileLineByHostname(hostname string) error { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + if !h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = true + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + return nil + } + + return ErrAlredyCommentedLine + } + } + } + + return ErrHostnameNotFound +} + +func (h *HostsFile) CommentHostsFileLinesByHostname(hostname string) { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + if !h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = true + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + } + } + } + } +} + +func (h *HostsFile) CommentHostsFileLinesByHostnameAsRegexp(hostname string) { + h.Lock() + defer h.Unlock() + + reg := regexp.MustCompile(hostname) + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if reg.MatchString(hn) { + if !h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = true + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + continue + } + } + } + } +} + +//UncommentHostsFileLineByRow set the IsCommented bit for the given row to false +func (h *HostsFile) UncommentHostsFileLineByRow(row int) error { + h.Lock() + defer h.Unlock() + + if len(h.HostsFileLines) > row { + if h.HostsFileLines[row].Type == LineTypeAddress { + if h.HostsFileLines[row].IsCommented { + h.HostsFileLines[row].IsCommented = false + + h.HostsFileLines[row].Raw = h.RenderHostsFileLine(row) + return nil + } + + return ErrAlredyUncommentedLine + } + + return ErrNotAnAddressLine + } + + return ErrUnknown +} + +//UncommentHostsFileLineByIP set the IsCommented bit for the given address to false +func (h *HostsFile) UncommentHostsFileLineByIP(ip net.IP) error { + h.Lock() + defer h.Unlock() + + for idx, hfl := range h.HostsFileLines { + if net.IP.Equal(ip, hfl.Address) { + if h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = false + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + return nil + } + + return ErrAlredyUncommentedLine + } + } + + return ErrNotAnAddressLine +} + +func (h *HostsFile) UncommentHostsFileLinesByIP(ip net.IP) { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + if net.IP.Equal(ip, h.HostsFileLines[idx].Address) { + if h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = false + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + } + } + } +} + +//UncommentHostsFileLineByAddress set the IsCommented bit for the given address as string to false +func (h *HostsFile) UncommentHostsFileLineByAddress(address string) error { + ip := net.ParseIP(address) + + return h.UncommentHostsFileLineByIP(ip) +} + +func (h *HostsFile) UncommentHostsFileLinesByAddress(address string) { + ip := net.ParseIP(address) + h.UncommentHostsFileLinesByIP(ip) +} + +//UncommentHostsFileLineByHostname set the IsCommented bit for the given hostname to false +func (h *HostsFile) UncommentHostsFileLineByHostname(hostname string) error { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + if h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = false + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + return nil + } + + return ErrAlredyUncommentedLine + } + } + } + + return ErrHostnameNotFound +} + +func (h *HostsFile) UncommentHostsFileLinesByHostname(hostname string) { + h.Lock() + defer h.Unlock() + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if hn == hostname { + if h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = false + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + } + } + } + } +} + +func (h *HostsFile) UncommentHostsFileLinesByHostnameAsRegexp(hostname string) { + h.Lock() + defer h.Unlock() + + reg := regexp.MustCompile(hostname) + + for idx := range h.HostsFileLines { + for _, hn := range h.HostsFileLines[idx].Hostnames { + if reg.MatchString(hn) { + if h.HostsFileLines[idx].IsCommented { + h.HostsFileLines[idx].IsCommented = false + + h.HostsFileLines[idx].Raw = h.RenderHostsFileLine(idx) + continue + } + } + } + } +} diff --git a/vendor/github.com/areYouLazy/libhosty/parser.go b/vendor/github.com/areYouLazy/libhosty/parser.go new file mode 100644 index 000000000..9f090d09f --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/parser.go @@ -0,0 +1,112 @@ +package libhosty + +import ( + "io/ioutil" + "net" + "strings" +) + +//ParseHostsFile parse a hosts file from the given location. +// error is not nil if something goes wrong +func ParseHostsFile(path string) ([]HostsFileLine, error) { + byteData, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + return parser(byteData) +} + +//ParseHostsFileAsString parse a hosts file from a given string. +// error is not nil if something goes wrong +func ParseHostsFileAsString(stringData string) ([]HostsFileLine, error) { + bytesData := []byte(stringData) + return parser(bytesData) +} + +func parser(bytesData []byte) ([]HostsFileLine, error) { + byteDataNormalized := strings.Replace(string(bytesData), "\r\n", "\n", -1) + fileLines := strings.Split(byteDataNormalized, "\n") + hostsFileLines := make([]HostsFileLine, len(fileLines)) + + // trim leading an trailing whitespace + for i, l := range fileLines { + curLine := &hostsFileLines[i] + curLine.Number = i + curLine.Raw = l + + // trim line + curLine.trimed = strings.TrimSpace(l) + + // check if it's an empty line + if curLine.trimed == "" { + curLine.Type = LineTypeEmpty + continue + } + + // check if line starts with a # + if strings.HasPrefix(curLine.trimed, "#") { + // this can be a comment or a commented host line + // so remove the 1st char (#), trim spaces + // and try to parse the line as a host line + noCommentLine := strings.TrimPrefix(curLine.trimed, "#") + tmpParts := strings.Fields(strings.TrimSpace(noCommentLine)) + + // check what we have + switch len(tmpParts) { + case 0: + // empty line, comment line + curLine.Type = LineTypeComment + continue + default: + // non-empty line, try to parse as address + address := net.ParseIP(tmpParts[0]) + + // if address is nil this line is a comment + if address == nil { + curLine.Type = LineTypeComment + continue + } + } + + // otherwise it is a commented line so let's try to parse it as a normal line + curLine.IsCommented = true + curLine.trimed = noCommentLine + } + + // not a comment or empty line so try to parse it + // check if it contains a comment + curLineSplit := strings.SplitN(curLine.trimed, "#", 2) + if len(curLineSplit) > 1 { + // trim spaces from comments + curLine.Comment = strings.TrimSpace(curLineSplit[1]) + } + + curLine.trimed = curLineSplit[0] + curLine.Parts = strings.Fields(curLine.trimed) + + if len(curLine.Parts) > 1 { + // parse address to ensure we have a valid address line + tmpIP := net.ParseIP(curLine.Parts[0]) + if tmpIP != nil { + + curLine.Type = LineTypeAddress + curLine.Address = tmpIP + // lower case all + for _, p := range curLine.Parts[1:] { + curLine.Hostnames = append(curLine.Hostnames, strings.ToLower(p)) + } + + continue + } + } + + // if we can't figure out what this line is mark it as unknown + curLine.Type = LineTypeUnknown + } + + // normalize slice + hostsFileLines = hostsFileLines[:] + + return hostsFileLines, nil +} diff --git a/vendor/github.com/areYouLazy/libhosty/templates.go b/vendor/github.com/areYouLazy/libhosty/templates.go new file mode 100644 index 000000000..aa0bcacdf --- /dev/null +++ b/vendor/github.com/areYouLazy/libhosty/templates.go @@ -0,0 +1,52 @@ +package libhosty + +// linuxHostsTemplate defines default linux hosts file +const linuxHostsTemplate = `# Do not remove the following line, or various programs +# that require network functionality will fail. +127.0.0.1 localhost.localdomain localhost +::1 localhost6.localdomain6 localhost6 +` + +// windowsHostsTemplate defines default windows hosts file +const windowsHostsTemplate = `# Copyright (c) 1993-2006 Microsoft Corp. +# +# This is a sample HOSTS file used by Microsoft TCP/IP for Windows. +# +# This file contains the mappings of IP addresses to host names. Each +# entry should be kept on an individual line. The IP address should +# be placed in the first column followed by the corresponding host name. +# The IP address and the host name should be separated by at least one +# space. +# +# Additionally, comments (such as these) may be inserted on individual +# lines or following the machine name denoted by a '#' symbol. +# +# For example: +# +# 102.54.94.97 rhino.acme.com # source server +# 38.25.63.10 x.acme.com # x client host +# localhost name resolution is handle within DNS itself. +# 127.0.0.1 localhost +# ::1 localhost +` + +// darwinHostsTemplate defines default darwin hosts file +const darwinHostsTemplate = `## +# Host Database +# +# localhost is used to configure the loopback interface +# when the system is booting. Do not change this entry +## +127.0.0.1 localhost +255.255.255.255 broadcasthost::1 localhost + +::1 localhost +fe80::1%lo0 localhost +` + +// dockerDesktopTemplate defines docker desktop hosts entry +const dockerDesktopTemplate = `# Added by Docker Desktop +# To allow the same kube context to work on the host and the container: +127.0.0.1 kubernetes.docker.internal +# End of section +` diff --git a/vendor/modules.txt b/vendor/modules.txt index b11df6859..8bd796b44 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,6 +8,9 @@ github.com/Microsoft/go-winio/pkg/guid # github.com/apparentlymart/go-cidr v1.1.0 ## explicit github.com/apparentlymart/go-cidr/cidr +# github.com/areYouLazy/libhosty v1.1.0 +## explicit; go 1.16 +github.com/areYouLazy/libhosty # github.com/containers/winquit v1.1.0 ## explicit; go 1.19 github.com/containers/winquit/pkg/winquit