Compare commits

...

19 Commits

Author SHA1 Message Date
Justin Reichardt c0cf2abdd7 Correctly finds certificate
Had a + instead of a , to seperate arguments in the Join function
So it would never find the crt
2023-03-06 19:20:48 -06:00
Justin Reichardt ed9d23ceb9 Can now generate TLS certificates
They do not yet have the host information yet and are not accepted by anyone
making them very useless
2023-03-06 19:06:46 -06:00
Justin Reichardt 7c56c5d5c7 Manages handling fewer entries than cores safely
If the number of entries is fewer than the number of cores then it is being
assumed that you can run this on a single core.
2023-03-06 19:04:44 -06:00
Justin Reichardt 8bc1777b87 Removes duplicate entries using all cores 2023-03-06 19:04:44 -06:00
Justin Reichardt 16e28ee13a Added removing whitelisted words to removing safewords
Again this is to assist with splitting the removing duplicates into multiple go routines
2023-03-06 19:04:44 -06:00
Justin Reichardt 8bb577ee5c Seperated removing safewords and regular words
This will help when setting up for splitting the duplicate check into go routines
2023-03-06 19:04:44 -06:00
Justin Reichardt deca6fcdec Added helpful comments to hosts 2023-03-06 19:04:44 -06:00
Justin Reichardt db065b2fb9 Added more useful help message 2023-03-06 19:04:44 -06:00
Justin Reichardt 0bec84b137 Added webserver flag for starting one
Later when the testing is done, the webserver will be enabled by default in the
config. This will allow the user to decide which mode they want to use.
2023-03-06 19:04:44 -06:00
Justin Reichardt 2a7cec0008 Added the webserver option to the config
This allows turning the web server on or off
2023-03-06 19:04:44 -06:00
Justin Reichardt c3ff05c989 Cleaned up the cfg file
I did a bunch of convoluted things to achieve something simple
Also added some comments
2023-03-06 19:04:44 -06:00
Justin Reichardt 2ae0f15b82 serve now pulls config and config panics if it fails to create
Had to also add var to allow serve to pull certs

If it cannot manage the config it needs to exit, so it made more sense to add it
there then check every time it is called
2023-03-06 19:04:44 -06:00
Justin Reichardt 15ee33eb01 hosts now pulls the config itself 2023-03-06 19:04:44 -06:00
Justin Reichardt 83679a1033 Added sys to cfg
Creating a single cfg that can be passed around instead of several variables
2023-03-06 19:04:44 -06:00
Justin Reichardt e1c576f2a1 Performed some cleanup
Mostly go formatting but a little creative spacing and comments
2023-03-06 19:04:44 -06:00
Justin Reichardt 77c020e434 Removed uneeded args from hosts.Update 2023-03-06 19:04:44 -06:00
Justin Reichardt ba490c27d7 Split config and hosts into separate modules
This cleans up the main file to make things easier to work with
2023-03-06 19:04:44 -06:00
Justin Reichardt 75207c51df Added a simple http server
This will later be used for serving false content
2023-03-06 19:04:44 -06:00
Justin Reichardt e2eb14dbc8 Now detects the OS at compilation
Using build tags was a more elegant way of determining the OS. Hopefully making
less room for errors and easier configuration later

Also it is now a negligibly smaller binary
2023-03-06 19:04:44 -06:00
8 changed files with 1036 additions and 481 deletions

148
src/cfg/cfg.go Normal file
View File

@ -0,0 +1,148 @@
package cfg
import (
"bufio"
"log"
"os"
"strconv"
)
const CFG = `
# There are 3 types of entries: download, site, and whitelist. Downloads are
# downloaded and stripped of comments and bad entries if possible before being
# added to a list of sites. Whitelisted urls are removed from the list of sites.
# From there all the urls are added to the hosts file for both IPv4 and IPv6.
# You can also add comments by prepending with a '#'.
# This is a static entry
#site=www.site.xyz
# This is a download entry
#download=w3.site.xyz/location/to/config.txt
# This is a whitelist entry
#whitelist=www.site.xyz
# A suggested download is: https://github.com/StevenBlack/hosts
#download=https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
# Webserver
# This will allow you to intercept and alter messages
# It is disabled by default
#Webserver=true`
type Config struct {
CfgLoc string
Sites []string
Downloads []string
Whitelist []string
System struct {
OS string
TmpDir string
HostsLoc string
CfgLoc string
Var string
}
WebServer struct {
Enabled bool
}
}
// Used to hold a list of functions to be run when building
var configFuncs []func(*Config)
// Create initialized a config to be used the entire session
func Create() (cfg Config) {
for _,fp := range(configFuncs){
fp(&cfg)
}
if (cfg.System.OS == ""){log.Fatal("Failed to detect the OS")}
err, cfg := cfg.Update()
if (err != nil){log.Fatal("Failed to handle the config file: " + err.Error())}
return
}
// cfgparse recieves the location of the config file and returns a list of sites to add and content to download
func (cfg Config) Update() (error, Config) {
// Opening the config file
l := (cfg.System.CfgLoc + "rhosts.cfg")
var err error = nil
log.Print("Opening: ", l)
if _, err = os.Stat(cfg.System.CfgLoc); os.IsNotExist(err) {
log.Print(cfg.System.CfgLoc + " Does not exist, attempting to create it")
err = os.MkdirAll(cfg.System.CfgLoc, 0755)
if err != nil {
log.Fatal("Could not create " + cfg.System.CfgLoc)
}
}
// Create one if it doesn't exist
// This is done after so that it doesn't read the default one
// in the event one doesn't exist
if _, err = os.Stat(l); os.IsNotExist(err) {
log.Print(l + " does not exist, attempting to create a placeholder")
err = os.WriteFile(l, []byte(CFG), 0644)
if err != nil {
log.Fatal("Unable to create file: " + l)
}
}
file, err := os.Open(l)
defer file.Close()
if err != nil {
log.Print(err)
return err, cfg
}
filebuf := bufio.NewScanner(file)
filebuf.Split(bufio.ScanLines)
for i , res := 0,filebuf.Scan(); res; res = filebuf.Scan() {
i++
buf := filebuf.Text()
if (cfgparseline(buf, &cfg) == true){
log.Fatal("Failed to read line: " + strconv.Itoa(i) + ": " + buf)
}
}
err = filebuf.Err()
if err != nil {
log.Print(err)
return err, cfg
}
return err, cfg
}
// cfgparseline reads a single line of the config and returns the type and content of the line
func cfgparseline(buf string, cfg *Config) (fail bool) {
if len(buf) == 0 {
return
}
switch buf[0] {
case ' ':
case '#':
return
case 'd':
if (len(buf) > 10 && buf[0:9] == "download=") {
cfg.Downloads = append(cfg.Downloads, buf[9:])
} else {
fail = true
}
case 's':
if (len(buf) > 6 && buf[0:5] == "site=") {
cfg.Sites = append(cfg.Sites, buf[5:])
} else {
fail = true
}
case 'w':
if (len(buf) > 10 && buf[0:10] == "whitelist=") {
cfg.Whitelist = append(cfg.Whitelist, buf[9:])
} else if (len(buf) > 10 && buf[0:10] == "webserver=") {
if (len(buf[10:]) >= 4 && buf[10:] == "true"){
cfg.WebServer.Enabled = true
}else if (len(buf[10:]) >= 5 && buf[10:] == "false"){
cfg.WebServer.Enabled = false
}else {fail = true}
} else {
fail = true
}
}
return
}

16
src/cfg/linux.go Normal file
View File

@ -0,0 +1,16 @@
//go:build linux
// +build linux
package cfg
func init() {
fp := func(c *Config) {
c.System.OS = "linux"
c.System.TmpDir = "/tmp/"
c.System.HostsLoc = "/etc/hosts"
c.System.CfgLoc = "/etc/rhosts/"
c.System.Var = "/var/lib/rhosts/"
}
configFuncs = append(configFuncs, fp)
}

20
src/cfg/windows.go Normal file
View File

@ -0,0 +1,20 @@
//go:build windows
// +build windows
package sys
func init() {
}
func init() {
fp := func(c *Config) {
c.System.os = "windows"
c.System.tmpdir = "/tmp/"
c.System.hostsloc = "/Windows/System32/drivers/etc/hosts"
c.System.cfgloc = "/ProgramData/rhosts/"
c.System.Var = "/ProgramData/rhosts/"
}
configFuncs = append(configFuncs, fp)
}

384
src/hosts/hosts.go Normal file
View File

@ -0,0 +1,384 @@
package hosts
import (
"bufio"
"io"
"jbreich/rhosts/cfg"
"log"
"net/http"
"os"
"runtime"
)
// siteList holds the location of all the sites along with a list of their location
type siteList struct {
location string
siteEntry []siteEntry
}
// siteEntry holds a single entry and if it is a repeat
type siteEntry struct {
repeat bool
site string
}
type siteEntryPointer struct {
r *bool
s *string
}
func Update() (err error) {
config := cfg.Create()
var siteBuff []siteList
err = error(nil)
err = copystatichosts(config.System.TmpDir, config.System.HostsLoc)
if err != nil {
log.Print("Failed to copy static entries")
return
}
defer os.Remove(config.System.TmpDir + "rhosts")
err, siteBuff = downloadcontent(config.Downloads, config.System.TmpDir, config.System.HostsLoc)
if err != nil {
log.Print("Failed to download entries")
return
}
err = writesites(config.Sites, config.System.TmpDir, &siteBuff)
if err != nil {
log.Print("Failed to failed to copy rhosts static entries")
return
}
removeUnwanted(&siteBuff, &config.Whitelist)
err = write2tmp(config.System.TmpDir, &siteBuff)
if err != nil {
log.Print("Failed to write sites to tmpfile")
return
}
err = writetmp2hosts(config.System.HostsLoc, config.System.TmpDir)
if err != nil {
log.Print("Failed to copy to hosts file")
return
}
log.Print("Finished updating host")
return
}
// copystatichosts copies the hosts not managed by rhosts from the hosts file to the start of the temporary file
func copystatichosts(tmpdir, hostsloc string) error {
fileloc := tmpdir + "rhosts"
file, err := os.Create(fileloc)
defer file.Close()
if err != nil {
log.Print(err)
return err
}
filer, err := os.Open(hostsloc)
defer filer.Close()
if err != nil {
log.Print(err)
return err
}
filebuf := bufio.NewScanner(filer)
filebuf.Split(bufio.ScanLines)
for res := filebuf.Scan(); res; res = filebuf.Scan() {
buff := filebuf.Text()
if buff == "# rhosts begin" {
break
}
_, err := file.WriteString(buff + "\n")
if err != nil {
log.Print(err)
return err
}
}
_, err = file.WriteString("# rhosts begin\n")
err = filebuf.Err()
return err
}
// downloadcontent attempts to download the provided url and create a siteList. If the file fails to download it attempts to find an old copy from the hosts file.
func downloadcontent(downloads []string, tmpdir string, hostsloc string) (err error, list []siteList) {
for _, d := range downloads {
var site siteList
site.location = d
log.Print("Downloading: ", d)
response, err := http.Get(d)
if err != nil {
log.Print(err)
log.Print("Looking for old record in hosts file")
downloadoldlookup(hostsloc, d, &site)
} else {
defer response.Body.Close()
scanner := bufio.NewScanner(response.Body)
for scanner.Scan() {
resp := checkDownloadLine(scanner.Text())
if resp.site != "" {
site.siteEntry = append(site.siteEntry, resp)
}
}
}
list = append(list, site)
}
return err, list
}
// checkDownloadLine parses the download line into just the address that needs to be blocked
func checkDownloadLine(line string) (address siteEntry) {
var token []string
address.repeat = false
address.site = ""
buff := ""
lineLength := len(line) - 1
for i, c := range line {
if c != ' ' && i < lineLength {
buff += string(c)
} else if len(buff) > 0 {
if i == lineLength {
buff += string(c)
}
token = append(token, buff)
buff = ""
}
}
if len(token) == 0 {
return
}
if token[0][0] == '#' {
return
}
for _, t := range token {
var period uint
var failed bool
period = 0
failed = false
for _, c := range t {
switch c {
case '.':
period++
case '#':
return
case ':':
failed = true
break
}
}
if period <= 2 && failed == false {
address.site = t
return
}
}
return
}
// downloadoldlookup attemps to find an entry in the hosts file and add it to the siteList
func downloadoldlookup(hostsloc, d string, site *siteList) error {
var err error = nil
var state uint8 = 0
hostsf, err := os.Open(hostsloc)
if err != nil {
log.Print(err)
return err
}
defer hostsf.Close()
fbuff := bufio.NewScanner(hostsf)
fbuff.Split(bufio.ScanLines)
for res := fbuff.Scan(); res; res = fbuff.Scan() {
buff := fbuff.Text()
switch state {
case 0:
if buff == "# rhosts download - "+d {
log.Print("Found old record in hosts file:" + buff)
state = 1
}
case 1:
if len(buff) >= 9 && buff[0:8] == "# rhosts" {
state = 2
} else {
siteBuff := checkDownloadLine(buff)
if siteBuff.site != "" {
site.siteEntry = append(site.siteEntry, siteBuff)
}
}
case 3:
return nil
}
}
return err
}
// writesites writes the list of sites from the config file to the local siteList
func writesites(sites []string, tmpdir string, siteBuff *[]siteList) (err error) {
var localList siteList
localList.location = "local"
err = nil
fileloc := tmpdir + "rhosts"
log.Print("Opening: " + fileloc)
if len(sites) == 0 {
return
}
for _, s := range sites {
var site siteEntry
site.repeat = false
site.site = s
localList.siteEntry = append(localList.siteEntry, site)
}
*siteBuff = append(*siteBuff, localList)
return
}
// removeUnwanted removes any duplicate or uneeded/unwanted addresses
func removeUnwanted(siteBuff *[]siteList, whitelist *[]string) {
// Words that will be remove automatically, they hold significant importance
var safewords = []string{"localhost", "localhost.localdomain", "broadcasthost", "ip6-loopback", "ip6-localhost", "ip6-localnet", "ip6-mcastprefix", "ip6-allnodes", "ip6-allrouters", "ip6-allhosts", "local"}
// Counters for how many sites were not added because: downloads, safewords, whitelisted
var c struct {
d uint
s uint
w uint
}
c.d = 0
c.s = 0
c.w = 0
var entry []siteEntryPointer
var entryBuff siteEntryPointer
// Add downloads to buffered list of sites
for i := len((*siteBuff)) - 1; i > -1; i-- {
for j := len((*siteBuff)[i].siteEntry) - 1; j > -1; j-- {
entryBuff.r = &((*siteBuff)[i].siteEntry[j].repeat)
entryBuff.s = &((*siteBuff)[i].siteEntry[j].site)
entry = append(entry, entryBuff)
}
}
// Removing safewords and whitelisted words
log.Print("Checking for safewords and whitelisted words")
for i,e := range entry {
if (*e.r == true) {
continue
}
for _,w := range(safewords) {
if *e.s == w {
*(entry[i].r) = true
c.s++
break
}
}
for _, w := range *whitelist {
if *e.s == w {
*(entry[i].r) = true
c.w++
break
}
}
}
// Removing duplicates
log.Print("Checking for duplicates")
cores := runtime.NumCPU()
lenEntry := len(entry)
if (cores <= lenEntry){
fin := make(chan uint)
for i := cores; i > 0 ; i-- {
go removeDuplicate(entry[i-1:],cores, fin)
}
for ;cores > 0; cores-- {
count :=<-fin
c.d =+ count
}
} else {
removeDuplicate(entry,1, nil)
}
log.Printf("Total: %d\tDuplicates: %d\tSafeWords: %d\tWhitelisted: %d\n", lenEntry, c.d, c.s, c.w)
}
// RemoveDuplicate removes duplicates from a range
func removeDuplicate (entry []siteEntryPointer, freq int , done chan uint){
var cd uint
for i, e := range entry {
if (i+1)%freq != 0 {
continue
}
if *e.r == true || (i+1)%freq != 0 {
continue
}
for j, n := range entry[i+1:] {
if *e.r == true {
continue
}
if *e.s == *n.s {
*(entry[i+j].r) = true
cd++
}
}
}
if (done != nil){
done <- cd
}
}
// write2tmp write the siteBuff to the tempfile
func write2tmp(tmpdir string, siteBuff *[]siteList) (err error) {
err = nil
tmploc := tmpdir + "rhosts"
tmpf, err := os.OpenFile(tmploc, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
defer tmpf.Close()
if err != nil {
log.Print(err)
return err
}
for _, location := range *siteBuff {
if len(location.siteEntry) == 0 {
continue
}
_, err := tmpf.WriteString("# rhosts download - " + location.location + "\n")
if err != nil {
return err
}
for _, site := range location.siteEntry {
if site.repeat == false {
_, err = tmpf.WriteString("0.0.0.0 " + site.site + "\n")
if err != nil {
return err
}
_, err = tmpf.WriteString(":: " + site.site + "\n")
if err != nil {
return err
}
}
}
}
return
}
// writetmp2hosts overwrites the hostsfile with the tmp file
func writetmp2hosts(hostsloc, tmpdir string) error {
var err error = nil
tmploc := tmpdir + "rhosts"
hosts, err := os.Create(hostsloc)
if err != nil {
log.Print(err)
return err
}
tmp, err := os.Open(tmploc)
if err != nil {
log.Print(err)
return err
}
_, err = io.Copy(hosts, tmp)
if err != nil {
log.Print(err)
}
return err
}

View File

@ -17,33 +17,20 @@
* along with rhosts. If not, see <https://www.gnu.org/licenses/>.
*/
// rhosts - Program used to maintain a blocklist appended to a host file
// rhosts - Program used to maintain a blocklist appended to a host file
package main
import (
"runtime"
"os"
"io"
"bufio"
"log"
"net/http"
"flag"
"time"
"fmt"
"jbreich/rhosts/hosts"
"jbreich/rhosts/serve"
"log"
"time"
)
// siteList holds the location of all the sites along with a list of their location
type siteList struct {
location string
siteEntry []siteEntry
}
// siteEntry holds a single entry and if it is a repeat
type siteEntry struct {
repeat bool
site string
}
const GPL =`
const GPL = `
rhosts maintains a blocklist and appends it to the system hosts file
Copyright (C) 2021 Justin Reichardt
@ -61,19 +48,26 @@ const GPL =`
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
`
const usage = `
This program's settings are modified by the rhost.cfg file and by setting the following flags:
`
func main() {
tmpdir := ""
hostsloc := ""
cfgloc := ""
var daemon bool=false
var interval int=1440
var versionflag bool=false
var removetimestamp bool=false
var siteBuff []siteList
exit := make(chan bool)
var webserver bool = false
var daemon bool = false
var interval int = 1440
var versionflag bool = false
var removetimestamp bool = false
flag.Usage = func() { // [4]
fmt.Fprintf(flag.CommandLine.Output(), GPL + usage)
flag.PrintDefaults()
}
// Parsing Flags
flag.BoolVar(&webserver, "s", false, "Turn on the Webserver")
flag.BoolVar(&daemon, "d", false, "Should this be run in daemon mode")
flag.IntVar(&interval, "t", 1440, "Minutes until next run of daemon")
flag.BoolVar(&versionflag, "version", false, "show version information")
@ -89,474 +83,46 @@ func main() {
// Check if timestamp should be removed
if removetimestamp {
log.SetFlags(0)
}else{
} else {
// GPL information
fmt.Println(GPL)
}
if daemon {
log.Print("daemon:" , daemon)
log.Print("interval:",interval)
log.Print("daemon:", daemon)
log.Print("interval:", interval)
}
sysdetect (&tmpdir, &hostsloc, &cfgloc)
for true {
var sites, downloads, whitelist []string
err := error(nil)
err = cfgparse(&sites, &downloads, &whitelist, cfgloc)
if (err != nil){
log.Print("Failed to parse config file")
continue
}
err = copystatichosts(tmpdir, hostsloc)
if (err != nil){
log.Print("Failed to copy static entries")
continue
}
defer os.Remove(tmpdir + "rhosts")
err, siteBuff = downloadcontent(downloads, tmpdir, hostsloc)
if (err != nil){
log.Print("Failed to download entries")
continue
}
err = writesites(sites, tmpdir, &siteBuff)
if (err != nil){
log.Print("Failed to failed to copy rhosts static entries")
continue
}
removeduplicates(&siteBuff, &whitelist)
err = write2tmp(tmpdir, &siteBuff)
if (err != nil){
log.Print("Failed to write sites to tmpfile")
continue
}
err = writetmp2hosts(hostsloc, tmpdir)
if (err != nil){
log.Print("Failed to copy to hosts file")
continue
}
log.Print("Finished updating host")
if (daemon == true){
i := time.Now().Add(time.Duration(interval) * time.Minute).Format(time.Layout)
log.Printf("Sleeping for %d minutes", interval)
log.Print("Should restart at: " + i)
time.Sleep(time.Duration(interval) * time.Minute)
}else{
break
}
// Starting web server
if (webserver == true){
go serve.Start(exit)
defer func(){<-exit}()
}
}
// sysdetect determines which OS it is running on and set the default locations
func sysdetect (tmpdir, hostsloc, cfgloc *string) {
// Detect OS and set params
switch runtime.GOOS {
case "windows":
//log.Fatal("Windows is not supported")
*tmpdir = "/tmp"
*hostsloc = "/Windows/System32/drivers/etc/hosts"
*cfgloc = "/ProgramData/rhosts/"
case "linux":
*tmpdir = "/tmp/"
*hostsloc = "/etc/hosts"
*cfgloc ="/etc/rhosts/"
case "ios":
log.Fatal("IOS is not supported")
default:
log.Fatal(runtime.GOOS," is not supported")
}
}
// cfgparse recieves the location of the config file and returns a list of sites to add and content to download
func cfgparse (sites, downloads, whitelist *[]string, cfgloc string) (error){
l := (cfgloc + "rhosts.cfg")
var err error=nil
log.Print("Opening: ", l)
if _,err = os.Stat(cfgloc); os.IsNotExist(err) {
log.Print(cfgloc + " Does not exist, attempting to create it")
err = os.MkdirAll(cfgloc,0755)
// Update the hosts file
if daemon == false {
err := hosts.Update()
if err != nil {
log.Fatal("Could not create " + cfgloc)
}
}
if _,err = os.Stat(l); os.IsNotExist(err) {
log.Print(l + " does not exist, attempting to create a placeholder")
err = os.WriteFile(l,[]byte(CFG),0644)
if err != nil {
log.Fatal("Unable to create file: " + l)
}
}
file, err := os.Open(l)
defer file.Close()
if err != nil {
log.Print(err)
return err
}
filebuf := bufio.NewScanner(file)
filebuf.Split(bufio.ScanLines)
for res := filebuf.Scan();res;res = filebuf.Scan() {
state, body := cfgparseline(filebuf.Text())
switch state {
case 3:
*sites =append(*sites,body)
case 4:
*downloads = append(*downloads,body)
case 5:
*whitelist = append(*whitelist,body)
}
}
err = filebuf.Err()
if err != nil {
log.Print(err)
return err
}
return err
}
// cfgparseline reads a single line of the config and returns the type and content of the line
func cfgparseline(buf string) (uint8, string){
// State options
// 0 - Init
// 1 - Error
// 2 - Comment
// 3 - Site
// 4 - Download
// 5 - Whitelist
var state uint8= 0
body :=buf[:]
for i:=0; i<len(buf);i++ {
//fmt.Printf("%c",buf[i])
switch buf[i] {
case ' ':
case '#':
state = 2
case 'd':
if (len(buf) < i+10) {
state = 1
break
}
if (buf[i:(i+9)] == "download=") {
i +=9
state = 4
body = buf[i:]
} else{
state = 1
}
case 's':
if (len(buf) < i+6) {
state = 1
break
}
if (buf[i:(i+5)] == "site=") {
i +=5
state = 3
body = buf[i:]
} else{
state = 0
}
//compare buf[i:(i+3)] to "site"
case 'w':
if (len(buf) < i+10) {
state = 1
break
}
if (buf[i:(i+10)] == "whitelist=") {
i +=10
state = 5
body = buf[i:]
} else{
state = 1
}
}
if (state !=0){
return state,body
}
}
return state, body
}
// copystatichosts copies the hosts not managed by rhosts from the hosts file to the start of the temporary file
func copystatichosts(tmpdir, hostsloc string) error {
fileloc := tmpdir + "rhosts"
file, err := os.Create(fileloc)
defer file.Close()
if err != nil {
log.Print(err)
return err
}
filer, err := os.Open(hostsloc)
defer filer.Close()
if err != nil {
log.Print(err)
return err
}
filebuf := bufio.NewScanner(filer)
filebuf.Split(bufio.ScanLines)
for res := filebuf.Scan();res;res = filebuf.Scan() {
buff := filebuf.Text()
if (buff == "# rhosts begin"){
break
}
_,err := file.WriteString(buff + "\n")
if (err != nil) {
log.Print(err)
return err
}
}
_,err = file.WriteString("# rhosts begin\n")
err = filebuf.Err()
return err
}
// downloadcontent attempts to download the provided url and create a siteList. If the file fails to download it attempts to find an old copy from the hosts file.
func downloadcontent(downloads []string, tmpdir string, hostsloc string) (err error, list []siteList){
for _, d := range downloads {
var site siteList
site.location = d
log.Print("Downloading: ",d)
response, err := http.Get(d)
if (err !=nil) {
log.Print(err)
log.Print("Looking for old record in hosts file")
downloadoldlookup(hostsloc, d, &site)
}else{
defer response.Body.Close()
scanner := bufio.NewScanner(response.Body)
for scanner.Scan() {
resp := checkDownloadLine(scanner.Text())
if resp.site != "" {
site.siteEntry = append(site.siteEntry, resp)
}
}
}
list = append(list, site)
}
return err, list
}
} else {
// checkDownloadLine parses the download line into just the address that needs to be blocked
func checkDownloadLine (line string) (address siteEntry){
var token []string
address.repeat = false
address.site = ""
buff := ""
lineLength := len(line) -1
for i, c := range(line){
if c != ' ' && i < lineLength {
buff += string(c)
}else if len(buff) > 0 {
if i == lineLength{
buff += string(c)
}
token = append(token,buff)
buff = ""
}
}
if len(token) == 0 {
return
}
if token[0][0] == '#' {
return
}
for _, t := range(token) {
var period uint
var failed bool
period = 0
failed = false
for _, c := range(t) {
switch c{
case '.':
period ++
case '#':
return
case ':':
failed = true
break
}
}
if period <=2 && failed == false {
address.site = t
return
}
}
return
}
// downloadoldlookup attemps to find an entry in the hosts file and add it to the siteList
func downloadoldlookup(hostsloc, d string, site *siteList) error {
var err error = nil
var state uint8 = 0
hostsf, err := os.Open(hostsloc)
if (err != nil){
log.Print(err)
return err
}
defer hostsf.Close()
fbuff := bufio.NewScanner(hostsf)
fbuff.Split(bufio.ScanLines)
for res := fbuff.Scan();res;res = fbuff.Scan() {
buff := fbuff.Text()
switch state {
case 0:
if (buff == "# rhosts download - " + d){
log.Print("Found old record in hosts file:" + buff)
state =1
}
case 1:
if (len(buff) >=9 && buff[0:8] == "# rhosts"){
state = 2
}else{
siteBuff := checkDownloadLine(buff)
if siteBuff.site != "" {
site.siteEntry = append(site.siteEntry, siteBuff)
}
}
case 3:
return nil
}
}
return err
}
// writesites writes the list of sites from the config file to the local siteList
func writesites(sites []string, tmpdir string, siteBuff *[]siteList) (err error) {
var localList siteList
localList.location = "local"
err = nil
fileloc := tmpdir + "rhosts"
log.Print("Opening: " + fileloc)
if len(sites) == 0 {
return
}
for _,s := range sites {
var site siteEntry
site.repeat = false
site.site = s
localList.siteEntry = append(localList.siteEntry,site)
}
*siteBuff = append(*siteBuff,localList)
return
}
// removeduplicates removes any duplicate or uneeded/unwanted addresses
func removeduplicates(siteBuff *[]siteList, whitelist *[]string){
var safewords = []string{"localhost", "localhost.localdomain", "broadcasthost", "ip6-loopback", "ip6-localhost", "ip6-localnet", "ip6-mcastprefix", "ip6-allnodes", "ip6-allrouters", "ip6-allhosts", "local"}
var c struct {
d uint
s uint
w uint
}
c.d = 0
c.s = 0
c.w = 0
log.Print("Checking for duplicates")
var entry []struct{
r *bool
s *string
}
var entryBuff struct{
r *bool
s *string
}
for i := len((*siteBuff))-1; i > -1; i --{
for j := len((*siteBuff)[i].siteEntry)-1; j > -1; j -- {
entryBuff.r = &((*siteBuff)[i].siteEntry[j].repeat)
entryBuff.s = &((*siteBuff)[i].siteEntry[j].site)
entry = append(entry,entryBuff)
}
}
lenEntry := len(entry)
for i,e := range(entry) {
for _,w := range(safewords){
if *e.s == w {
*(entry[i].r) = true
c.s ++
break
}
}
if *(entry[i].r) == true {
continue
}
for _,w := range(*whitelist){
if *e.s == w {
*(entry[i].r) = true
c.w ++
break
}
}
if *(entry[i].r) == true {
continue
}
if i == lenEntry {
break
}
for j,n := range(entry[i+1:]){
if *e.s == *n.s {
*(entry[i+j].r) = true
c.d ++
}
}
}
log.Printf("Total: %d\tDuplicates: %d\tSafeWords: %d\tWhitelisted: %d\n", lenEntry, c.d, c.s, c.w)
}
// write2tmp write the siteBuff to the tempfile
func write2tmp(tmpdir string, siteBuff *[]siteList) (err error) {
err = nil
tmploc := tmpdir+ "rhosts"
tmpf, err := os.OpenFile(tmploc, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
defer tmpf.Close()
if err != nil {
log.Print(err)
return err
}
for _,location := range(*siteBuff){
if len(location.siteEntry) == 0 {
continue
}
_,err := tmpf.WriteString("# rhosts download - " + location.location + "\n")
for true {
err := hosts.Update()
if err != nil {
return err
log.Print(err)
}
for _,site := range(location.siteEntry){
if site.repeat == false {
_, err = tmpf.WriteString("0.0.0.0 " + site.site + "\n")
if err != nil {
return err
}
_, err = tmpf.WriteString(":: " + site.site + "\n")
if err != nil {
return err
}
// Check if daemon
if daemon == false {
break
}
if err == nil {
i := time.Now().Add(time.Duration(interval) * time.Minute).Format(time.Layout)
log.Printf("Sleeping for %d minutes", interval)
log.Print("Should restart at: " + i)
time.Sleep(time.Duration(interval) * time.Minute)
}
}
}
return
}
// writetmp2hosts overwrites the hostsfile with the tmp file
func writetmp2hosts(hostsloc, tmpdir string) error {
var err error = nil
tmploc := tmpdir + "rhosts"
hosts, err := os.Create(hostsloc)
if (err != nil){
log.Print(err)
return err
}
tmp, err := os.Open(tmploc)
if (err != nil){
log.Print(err)
return err
}
_,err = io.Copy(hosts,tmp)
if (err != nil){
log.Print(err)
}
return err
}

View File

@ -0,0 +1,233 @@
/*
Certutils manages certificates.
To Create a Certificate start with:
var ca CA
From there you will want to edit:
- Location
- Name
- X509Cert
- Signer (If it is suppose to be signed by another CA, you must add it before initializing)
Then initialize it:
ca.Initialize
Create the files:
ca.WriteToFile()
*/
package certutils
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"errors"
"log"
"math/big"
"os"
"path"
)
// I want to give Shane Utt credit for being the example for how this module creates and signs certs
/*
* CA holds information regarding a key pair and certificate
*/
type CA struct {
Location string // Location is the location of the keys
Name string // This is the name of the certificate, it will append ".key" and ".crt" when writing them:w
TLSConf *tls.Config
CRTPEM []byte // This will hold the certificate pem byte file until it is writen to files
PrivKeyPEM []byte // This will hold the private key pem byte file until it is writen to files
X509Cert *x509.Certificate // Make sure to change this to match what you want your certificate to say
Signer *CA // Signer is the address of who you want to sign this key
keyPair *rsa.PrivateKey
}
// If a location is given it will check for one at the location first.
// If one does not exist it will create one using the provided certificate.
// If one does exist it will check it against the certificate and decided if a new one needs to be made.
// If a Certificate is not provided then the default template will be used.
// The name it looks for is "ca": ca.key, ca.crt if no name is provided
func (ca CA) Initialize() (CA, error) {
var err error
// Check for name
if ca.Name == "" {
ca.Name = "ca"
}
// Check if CA Exists
var caBuf CA
caBuf, err = fillFromFile(ca)
if err == nil {
ca = caBuf
} else {
log.Print(err)
// Check for certificate
if ca.X509Cert == nil {
return ca, errors.New("No x509 certificate provided")
}
// If CA does not exist then create one
ca, err = ca.Build()
if err != nil {
return ca, err
}
}
return ca, err
}
func fillFromFile(ca CA) (CA, error) {
var err error
file := path.Join(ca.Location , ca.Name)
_, err = os.Stat(file + ".crt")
if err != nil {
return ca, err
}
ca.CRTPEM, err = os.ReadFile(file + ".crt")
if err != nil {
return ca, err
}
_, err = os.Stat(file + ".key")
if err != nil {
return ca, err
}
ca.PrivKeyPEM, err = os.ReadFile(file + ".key")
if err != nil {
return ca, err
}
log.Print("Reading keys from file")
// Decode crt pem
var p *pem.Block
p, _ = pem.Decode(ca.CRTPEM)
if p == nil {
return ca, errors.New("Failed to parse: " + file + ".crt")
}
certBuf, err := x509.ParseCertificate(p.Bytes)
if err != nil {
return ca, err
}
// This is where it needs to check if they match, for now it will just assign it
ca.X509Cert = certBuf
// Decode key pem
p, _ = pem.Decode(ca.PrivKeyPEM)
if p == nil {
return ca, errors.New("Failed to parse: " + file + ".key")
}
ca.keyPair, err = x509.ParsePKCS1PrivateKey(p.Bytes)
if err != nil {
return ca, err
}
return ca, err
}
// WriteToFile will create a key and crt file using the Location and Name on the CA
func (ca CA) WriteToFile() (err error) {
var file string
file = path.Join(ca.Location, ca.Name+".crt")
log.Print("Creating: " + file)
err = os.WriteFile(file, ca.CRTPEM, 0660)
if err != nil {
return
}
file = path.Join(ca.Location, ca.Name+".key")
log.Print("Creating: " + file)
err = os.WriteFile(file, ca.PrivKeyPEM, 0660)
return
}
// This is run when initializing unless the files already exist. You can force the building process here afterwards if you want to use the current one's x509 certificate but regenerate everything else.
func (ca CA) Build() (CA, error) {
log.Print("Generating key" + ca.Name)
var err error
// Changing the default serial number
// For some reason it uses 2019 as the default, I need to watch this in the event it changes
if ca.X509Cert.SerialNumber.Cmp(big.NewInt(2019)) == 0 {
// generate a random serial number
// Later should modify this to use issuerDN+serial
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return ca, err
}
ca.X509Cert.SerialNumber = serialNumber
}
// create our private and public key
caKeyPair, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return ca, err
}
ca.keyPair = caKeyPair
if ca.Signer == nil {
ca.Signer = &ca
}
// create the CA
caBytes, err := x509.CreateCertificate(rand.Reader, ca.X509Cert, ca.Signer.X509Cert, &caKeyPair.PublicKey, ca.Signer.keyPair)
if err != nil {
return ca, err
}
// pem encode
crtPEM := new(bytes.Buffer)
pem.Encode(crtPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})
privKeyPEM := new(bytes.Buffer)
pem.Encode(privKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(caKeyPair),
})
ca.CRTPEM = crtPEM.Bytes()
ca.PrivKeyPEM = privKeyPEM.Bytes()
return ca, err
}
func (ca CA) TlsConfigCreate() (*tls.Config, error) {
certpool := x509.NewCertPool()
if certpool.AppendCertsFromPEM(ca.CRTPEM) == false {
return nil, errors.New("Failed to AppendCertsFromPEM" + ca.Name)
}
TLSConf := &tls.Config{
RootCAs: certpool,
}
return TLSConf, nil
}

View File

@ -0,0 +1,79 @@
package certutils
import (
"testing"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"time"
"net"
)
// Parent template for all further certificates
var x509CATmpl = &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"Master CA of Localhost"},
Country: []string{"XX"},
Province: []string{""},
Locality: []string{"Your computer"},
StreetAddress: []string{""},
PostalCode: []string{"5432"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
var x509ServerTmpl = &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"The Website"},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"webserver"},
StreetAddress: []string{"bus 1"},
PostalCode: []string{"8"},
},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
func TestWriteToFile (t *testing.T){
var ca, server CA
var err error
ca.Name = "TestCertificateAuthority"
ca.X509Cert = x509CATmpl
ca, err = ca.Initialize()
if (err != nil){
t.Error("Failed to genrate the TestCertificateAuthority certificate" + err.Error())
}
server.Name = "server"
server.X509Cert = x509ServerTmpl
server.Signer = &ca
server, err = server.Initialize()
if (err != nil){
t.Error("Failed to generate the server certificate" + err.Error())
}
err = ca.WriteToFile()
if (err != nil){
t.Error("Failed to write the TestCertificateAuthority files" + err.Error())
}
err = server.WriteToFile()
if (err != nil){
t.Error("Failed to write the sever portion" + err.Error())
}
}

109
src/serve/serve.go Normal file
View File

@ -0,0 +1,109 @@
// Provides the web server for rhosts to relay altered content
package serve
import (
"crypto/x509"
"crypto/x509/pkix"
"net/http"
"jbreich/rhosts/serve/certutils"
"jbreich/rhosts/cfg"
"log"
"os"
"path"
"math/big"
"time"
"net"
)
func Start(exit chan bool) {
config := cfg.Create()
if config.WebServer.Enabled == false {
log.Print("Webserver was disabled in the config file")
exit <- true
return
}
go httpServer()
go httpsServer(path.Join(config.System.Var, "certs"))
}
func httpServer(){
err := http.ListenAndServe("127.0.0.1:80", http.HandlerFunc(httpHandler))
if (err != nil) {log.Fatal("Failed to start httls server")}
}
func httpsServer(certPath string) {
var err error
// Create certificates if they do not exist
// CA
err = os.MkdirAll(certPath,0755)
if (err != nil){log.Fatal("Could not create cert path: " + err.Error())}
var ca certutils.CA
ca.Location = certPath
ca.Name = "ca"
ca.X509Cert = &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"Rhost"},
Country: []string{""},
Province: []string{""},
Locality: []string{"Your computer"},
StreetAddress: []string{""},
PostalCode: []string{""},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
ca,err = ca.Initialize()
if(err!=nil){log.Fatal("Failed to build the ca: " + err.Error())}
err = ca.WriteToFile()
if(err!=nil){log.Fatal("Failed to write the ca: " + err.Error())}
// Server Certificate
var serverCa certutils.CA
serverCa.Location = certPath
serverCa.Name = "server"
serverCa.Signer = &ca
serverCa.X509Cert = &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"The Website"},
Country: []string{"US"},
Province: []string{""},
Locality: []string{"webserver"},
StreetAddress: []string{""},
PostalCode: []string{""},
},
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
serverCa,err = serverCa.Initialize()
if(err!=nil){log.Fatal("Failed to build the server certificate: " + err.Error())}
serverCa.WriteToFile()
if(err!=nil){log.Fatal("Failed to write the server certificate: " + err.Error())}
// Starting the server
err = http.ListenAndServeTLS("127.0.0.1:443", path.Join(certPath, "server.crt"), path.Join(certPath, "server.key"), http.HandlerFunc(httpHandler))
if (err != nil) {log.Fatal("Failed to start httls server: " + err.Error())}
return
}
func httpHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Test", 200)
}