diff --git a/404.html b/404.html new file mode 100644 index 0000000..507134e --- /dev/null +++ b/404.html @@ -0,0 +1,11 @@ + + + + + + + +

Project not found

+

Ensure the folder exists

+ + diff --git a/dnsmasq/dnsmasq.go b/dnsmasq/dnsmasq.go new file mode 100644 index 0000000..c1da55e --- /dev/null +++ b/dnsmasq/dnsmasq.go @@ -0,0 +1,80 @@ +package dnsmasq + +import ( + "fmt" + "log" + "net" + "strings" + + "github.com/miekg/dns" +) + +func Serve() { + // Create a new DNS server + server := &dns.Server{Addr: ":8989", Net: "udp"} + + // Set up the handler for DNS requests + dns.HandleFunc(".", handleRequest) + + // Start listening for incoming DNS requests + fmt.Println("Listening for DNS requests on port 8989...") + if err := server.ListenAndServe(); err != nil { + log.Fatalf("Failed to start DNS server: %v", err) + } +} + +func forward(r *dns.Msg) (*dns.Msg, error) { + // Forward the request to a real DNS server (for example, Google's public DNS server) + client := new(dns.Client) + realDNSAddr := "8.8.8.8:53" // Google's DNS server address + + // Send the request to the actual DNS server + response, _, err := client.Exchange(r, realDNSAddr) + if err != nil { + log.Printf("Failed to forward DNS request: %v", err) + return nil, err + } + + return response, nil +} + +func handleRequest(w dns.ResponseWriter, r *dns.Msg) { + domain := r.Question[0].Name + domain = strings.Trim(domain, ".") + parts := strings.Split(domain, ".") + tld := parts[len(parts)-1] + + response := dns.Msg{} + response.SetReply(r) + + // Check if it's a query for an A record (IPv4 address) + if "test" == tld { + log.Printf("Handling request for %s", domain) + for _, question := range r.Question { + if question.Qtype == dns.TypeA { + // Create a loopback A record pointing to 127.0.0.1 + rr := &dns.A{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 300, // TTL in seconds (5 minutes) + }, + A: net.ParseIP("127.0.0.1"), // Loopback address + } + + response.Answer = append(response.Answer, rr) + } + } + + w.WriteMsg(&response) + return + } + + // Send the response back to the original client + resp, err := forward(r) + if err != nil { + panic("We broke boys") + } + w.WriteMsg(resp) +} diff --git a/fastcgi/constants.go b/fastcgi/constants.go new file mode 100644 index 0000000..e5eb5f8 --- /dev/null +++ b/fastcgi/constants.go @@ -0,0 +1,49 @@ +package fastcgi + +type FCGIRecordType uint8 + +const FCGI_LISTENSOCK_FILENO uint8 = 0 +const FCGI_HEADER_LEN uint8 = 8 +const VERSION_1 uint8 = 1 +const FCGI_NULL_REQUEST_ID uint8 = 0 +const FCGI_KEEP_CONN uint8 = 1 +const doubleCRLF = "\r\n\r\n" + +const ( + FCGI_BEGIN_REQUEST FCGIRecordType = iota + 1 + FCGI_ABORT_REQUEST + FCGI_END_REQUEST + FCGI_PARAMS + FCGI_STDIN + FCGI_STDOUT + FCGI_STDERR + FCGI_DATA + FCGI_GET_VALUES + FCGI_GET_VALUES_RESULT + FCGI_UNKNOWN_TYPE + FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE +) + +const ( + FCGI_RESPONDER uint8 = iota + 1 + FCGI_AUTHORIZER + FCGI_FILTER +) + +const ( + FCGI_REQUEST_COMPLETE uint8 = iota + FCGI_CANT_MPX_CONN + FCGI_OVERLOADED + FCGI_UNKNOWN_ROLE +) + +const ( + FCGI_MAX_CONNS string = "MAX_CONNS" + FCGI_MAX_REQS string = "MAX_REQS" + FCGI_MPXS_CONNS string = "MPXS_CONNS" +) + +const ( + maxWrite = 65500 // 65530 may work, but for compatibility + maxPad = 255 +) diff --git a/fastcgi/record.go b/fastcgi/record.go new file mode 100644 index 0000000..85e8c87 --- /dev/null +++ b/fastcgi/record.go @@ -0,0 +1,88 @@ +package fastcgi + +import ( + "bytes" + "encoding/binary" +) + +// for padding so we don't have to allocate all the time +// not synchronized because we don't care what the contents are +var pad [maxPad]byte + +// A record is essentially a "packet" in FastCGI. +// The header lets the server know what type +// of data is being sent, and it expects +// a certain structure depending on +// the type. +type Header struct { + Version uint8 + Type FCGIRecordType + Id uint16 + ContentLength uint16 + PaddingLength uint8 + Reserved uint8 +} + +type Record struct { + Header Header + Content []byte + ReadBuffer []byte // Buffer to use when reading a response +} + +// Turn a record into a byte array so it can be +// sent over the network. The byte array will +// be in the shape/order that is expected +// in the FastCGI protocol. +func (r *Record) toBytes() ([]byte, error) { + var buf bytes.Buffer + + if err := binary.Write(&buf, binary.BigEndian, r.Header); err != nil { + return nil, err + } + if _, err := buf.Write(r.Content); err != nil { + return nil, err + } + if _, err := buf.Write(pad[:r.Header.PaddingLength]); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func (req *FCGIRequest) NewRecord(t FCGIRecordType, content []byte) *Record { + r := Record{} + + r.Header.Version = 1 + r.Header.Type = t + r.Header.Id = req.Id + r.Header.ContentLength = uint16(len(content)) + r.Header.PaddingLength = 0 + r.Content = content + + return &r +} + +// FCGI_BEGIN_REQUEST record should +// have a body of 8 bytes with: +// - The first byte being the role +// - The second byte being also the role +// - The third byte being the flags +// - The last five bytes are reserved for future use +func (req *FCGIRequest) NewBeginRequestRecord() *Record { + role := uint16(FCGI_RESPONDER) + flags := byte(0) + // Create an 8-byte array as per the FastCGI specification. + var b [8]byte + + // Split the 16-bit role into two bytes and assign them. + b[0] = byte(role >> 8) // High byte + b[1] = byte(role) // Low byte + + // Set the flags. + b[2] = flags + + // The reserved bytes (b[3] to b[7]) will remain zero by default. + + // Return a begin request record + return req.NewRecord(FCGI_BEGIN_REQUEST, b[:]) +} diff --git a/fastcgi/request.go b/fastcgi/request.go index c5679b0..8baaa80 100644 --- a/fastcgi/request.go +++ b/fastcgi/request.go @@ -23,12 +23,12 @@ func RequestFromHttp(r *http.Request) *FCGIRequest { c.Context = make(map[string]string) c.Context["SERVER_SOFTWARE"] = "oasis / fastcgi" c.Context["QUERY_STRING"] = r.URL.RawQuery - c.Context["REMOTE_ADDR"] = "127.0.0.1" + c.Context["REMOTE_ADDR"] = r.Host c.Context["REQUEST_METHOD"] = r.Method c.Context["REQUEST_URI"] = r.URL.Path - c.Context["SERVER_ADDR"] = "localhost" - c.Context["SERVER_PORT"] = "8000" - c.Context["SERVER_NAME"] = "localhost" + c.Context["SERVER_ADDR"] = r.Host + c.Context["SERVER_PORT"] = "80" + c.Context["SERVER_NAME"] = r.Host // HTTP headers should be sent as FCGI_PARAMS. // We have to turn the name of the header diff --git a/go.mod b/go.mod index 9eb89d0..12fa65f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,12 @@ module github.com/javif89/oasis go 1.23.4 + +require ( + github.com/miekg/dns v1.1.62 // indirect + golang.org/x/mod v0.18.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/tools v0.22.0 // indirect +) diff --git a/go.sum b/go.sum index e69de29..95e8194 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= diff --git a/main.go b/main.go index 72b035d..50a7742 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,9 @@ import ( "net/http" "os" "path/filepath" + "strings" + "github.com/javif89/oasis/dnsmasq" "github.com/javif89/oasis/fastcgi" ) @@ -20,9 +22,30 @@ func fileExists(path string) bool { return true } +func projectFolderExists(path string) bool { + _, err := os.Stat(path) + if os.IsNotExist(err) { + return false + } + + return true +} + func handleRequest(w http.ResponseWriter, r *http.Request) { fmt.Println("Request received") - root := "/home/javi/projects/javierfeliz.com/public/" + + log.Printf("Host: %s", r.Host) + folder := strings.ReplaceAll(r.Host, ".test", "") + searchPath := "/home/javi/projects" + + log.Printf("Checking %s for project", filepath.Join(searchPath, folder)) + + if !projectFolderExists(filepath.Join(searchPath, folder)) { + http.ServeFile(w, r, "./404.html") + return + } + + root := filepath.Join(searchPath, folder, "public") // We will first try checking if the file exists // in case a static file is being requested @@ -82,9 +105,12 @@ func main() { // Server http.HandleFunc("/", handleRequest) - fmt.Println("Starting server") + fmt.Println("Starting DNSMasq Server") + go dnsmasq.Serve() - err := http.ListenAndServe(":8000", nil) + fmt.Println("Starting HTTP Server") + + err := http.ListenAndServe(":80", nil) if err != nil { log.Fatal(err) } diff --git a/resp.txt b/resp.txt new file mode 100644 index 0000000..4bb197f --- /dev/null +++ b/resp.txt @@ -0,0 +1,263 @@ +Content-Type: text/html; charset=UTF-8 +Cache-Control: no-cache, private +Date: Tue, 31 Dec 2024 07:44:10 GMT +Set-Cookie: XSRF-TOKEN=eyJpdiI6InlVV1doeUhGS1lvZVJ6RTdNR3NLZ0E9PSIsInZhbHVlIjoiRmdmSUN2NHRYbkNBVlJodk4rcWgwY3VKd1BmQ2dIVGp4aFZPRm5xaEV0T0ZWd3Z6SXhXK1RneEZTYWEyUTlpY1JYTU5nWmVkRjhnYUtHbEtydGZ6ZTM2LytqMEZoL3Y5NTlGMFNpa1dSbzhuTTdYYXl1T2VYYmdHK3ZMOE5RZy8iLCJtYWMiOiI4YTA3NTY2NDM4NzkzODg2YmMzY2EwMzhlNGQ5YWU0NWI0Mjk1YjZlYjU1N2NkMjM0ZWIxNTVhZTViZTA4NDE1IiwidGFnIjoiIn0%3D; expires=Tue, 31 Dec 2024 09:44:10 GMT; Max-Age=7200; path=/; samesite=lax +Set-Cookie: laravel_session=eyJpdiI6IkZybDVvc3pDWGZ5ZjF1STNVTi9NUEE9PSIsInZhbHVlIjoiM0lHVTN3SGtDVVdYQ3hIRXd6VlBNVDAzSE9MMnVzVFhPa2xmWloxUnRNdm82eXhjT0pBS1hqUGNWVlZTNDFZc0FjVTR6U05sdy9FVjBLWjNKMUxCVnN2RTNQYy9wWVZmRkg2STVaRkY2M0JLSThybml1ajlBZjlSdHRoS1EySkoiLCJtYWMiOiJhMzIzNDgzZTc2NjZjZTVjZDcwNGUzNDU2MjY3OTk2OGU5YmY1Y2UyNjgwMDZmMGFjMjA0MmZjZWY5NzIwMmU1IiwidGFnIjoiIn0%3D; expires=Tue, 31 Dec 2024 09:44:10 GMT; Max-Age=7200; path=/; httponly; samesite=lax + + + + + + + + + + + + + + + + + + Javier Feliz - Home + + + + + + + + + + + + + +
+
+ +
+
+ +
+

I'm a software engineer with a passion for robust, elegant, and simple solutions.

+

I write about +Laravel, +Go, +PHP, +Data Engineering and +Software Architecture, as well as my +general thoughts on software development.

+

I also make music as Scry (solo project) and Parti Papi (duo w/ a friend).

+

Thank you for coming to my little corner of the internet. I hope you like the content!

+
+ +
+
+
+ + + +