diff --git a/README.md b/README.md index 11cd9adc47f..71f4cd4c2e8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ THIS IS A FORK OF CADDY v1 - EVERYTHING IS STRIPPED EXCEPT THE PIECES NEEDED IN COREDNS. +Issues are not enabled in this repository. Please raise any issues in coredns/coredns. + +--- + Caddy is a **production-ready** open-source web server that is fast, easy to use, and makes you more productive. diff --git a/caddy.go b/caddy.go index eaec7bc453a..a868ae06df8 100644 --- a/caddy.go +++ b/caddy.go @@ -1,5 +1,7 @@ // Copyright 2015 Light Code Labs, LLC // +// Copyright 2024 MWS +// // 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 @@ -218,22 +220,22 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) { } // Add file descriptors of all the sockets that are capable of it - restartFds := make(map[string]restartTriple) + restartFds := make(map[string][]restartTriple) for _, s := range i.servers { gs, srvOk := s.server.(GracefulServer) ln, lnOk := s.listener.(Listener) pc, pcOk := s.packet.(PacketConn) if srvOk { if lnOk && pcOk { - restartFds[gs.Address()] = restartTriple{server: gs, listener: ln, packet: pc} + restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, listener: ln, packet: pc}) continue } if lnOk { - restartFds[gs.Address()] = restartTriple{server: gs, listener: ln} + restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, listener: ln}) continue } if pcOk { - restartFds[gs.Address()] = restartTriple{server: gs, packet: pc} + restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, packet: pc}) continue } } @@ -484,7 +486,7 @@ func Start(cdyfile Input) (*Instance, error) { return inst, nil } -func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error { +func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string][]restartTriple) error { // save this instance in the list now so that // plugins can access it if need be, for example // the caddytls package, so it can perform cert @@ -684,7 +686,7 @@ func executeDirectives(inst *Instance, filename string, return nil } -func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error { +func startServers(serverList []Server, inst *Instance, restartFds map[string][]restartTriple) error { errChan := make(chan error, len(serverList)) // used for signaling to error logging goroutine to terminate @@ -734,7 +736,16 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res // reuse the listener for a graceful restart. if gs, ok := s.(GracefulServer); ok && restartFds != nil { addr := gs.Address() - if old, ok := restartFds[addr]; ok { + // Multiple servers may use the same addr (SO_REUSEPORT option set), so it's important to ensure + // that we don't reuse the same listener/packetconn. + // We'll create new listeners in case there are no more available triples for the same address. + if triples, ok := restartFds[addr]; ok && len(triples) > 0 { + // Take first available triple + old := triples[0] + // Remove reused triple from restartFds + triples[0] = triples[len(triples)-1] + restartFds[addr] = triples[:len(triples)-1] + // listener if old.listener != nil { file, err := old.listener.File() diff --git a/caddyfile/parse.go b/caddyfile/parse.go index 32d7a2b5fc1..bd32934646d 100644 --- a/caddyfile/parse.go +++ b/caddyfile/parse.go @@ -51,12 +51,17 @@ func allTokens(input io.Reader) ([]Token, error) { type parser struct { Dispenser - block ServerBlock // current server block being parsed - validDirectives []string // a directive must be valid or it's an error - eof bool // if we encounter a valid EOF in a hard place - definedSnippets map[string][]Token + block ServerBlock // current server block being parsed + validDirectives []string // a directive must be valid or it's an error + eof bool // if we encounter a valid EOF in a hard place + definedSnippets map[string][]Token + snippetExpansions int // counts snippet imports expanded during this parse + fileExpansions int // counts file/glob imports expanded during this parse } +// maxSnippetExpansions is a hard cap to prevent excessively deep or cyclic snippet imports. +const maxSnippetExpansions = 1000 + func (p *parser) parseAll() ([]ServerBlock, error) { var blocks []ServerBlock @@ -108,6 +113,16 @@ func (p *parser) begin() error { if err != nil { return err } + // minimal guard: detect trivial self-import in snippet body + for i := 0; i+1 < len(tokens); i++ { + if tokens[i].Text == "import" { + // Only consider it an import directive if at start of a line + atLineStart := i == 0 || tokens[i-1].File != tokens[i].File || tokens[i-1].Line != tokens[i].Line + if atLineStart && replaceEnvVars(tokens[i+1].Text) == name { + return p.Errf("maximum snippet import depth (%d) exceeded", maxSnippetExpansions) + } + } + } p.definedSnippets[name] = tokens // empty block keys so we don't save this block as a real server. p.block.Keys = nil @@ -245,10 +260,18 @@ func (p *parser) doImport() error { tokensAfter := p.tokens[p.cursor+1:] var importedTokens []Token - // first check snippets. That is a simple, non-recursive replacement + // first check snippets. Count expansion and enforce cap. if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil { + if p.snippetExpansions >= maxSnippetExpansions { + return p.Errf("maximum snippet import depth (%d) exceeded", maxSnippetExpansions) + } + p.snippetExpansions++ importedTokens = p.definedSnippets[importPattern] } else { + if p.fileExpansions >= maxSnippetExpansions { + return p.Errf("maximum file import depth (%d) exceeded", maxSnippetExpansions) + } + p.fileExpansions++ // make path relative to the file of the _token_ being processed rather // than current working directory (issue #867) and then use glob to get // list of matching filenames diff --git a/caddyfile/parse_test.go b/caddyfile/parse_test.go index 2b1409192fd..9a12e5965a5 100644 --- a/caddyfile/parse_test.go +++ b/caddyfile/parse_test.go @@ -744,3 +744,60 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) { t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) } } + +func TestSnippetImportCycle(t *testing.T) { + tests := []struct { + name string + input string + }{ + { + name: "direct self-import", + input: ` + (loop) { + import loop + } + import loop + `, + }, + { + name: "self-import via recursion", + input: ` + (loop) { + { } + import loop + } + import loop + `, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := testParser(tt.input) + _, err := p.parseAll() + if err == nil { + t.Fatalf("expected error for snippet self-import cycle, got nil") + } + }) + } +} + +func TestFileImportCycleError(t *testing.T) { + fileA := writeStringToTempFileOrDie(t, "") + defer os.Remove(fileA) + fileB := writeStringToTempFileOrDie(t, "") + defer os.Remove(fileB) + + if err := ioutil.WriteFile(fileA, []byte("import "+fileB), 0644); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(fileB, []byte("import "+fileA), 0644); err != nil { + t.Fatal(err) + } + + p := testParser("import " + fileA) + _, err := p.parseAll() + if err == nil { + t.Fatalf("expected error for file import cycle, got nil") + } +} diff --git a/plugins.go b/plugins.go index 51d6739707c..a4f70c46b4a 100644 --- a/plugins.go +++ b/plugins.go @@ -56,33 +56,13 @@ var ( func DescribePlugins() string { pl := ListPlugins() - str := "Server types:\n" - for _, name := range pl["server_types"] { - str += " " + name + "\n" - } - - str += "\nCaddyfile loaders:\n" - for _, name := range pl["caddyfile_loaders"] { - str += " " + name + "\n" - } - - if len(pl["event_hooks"]) > 0 { - str += "\nEvent hook plugins:\n" - for _, name := range pl["event_hooks"] { - str += " hook." + name + "\n" - } - } - - if len(pl["clustering"]) > 0 { - str += "\nClustering plugins:\n" - for _, name := range pl["clustering"] { - str += " " + name + "\n" - } - } - - str += "\nOther plugins:\n" + str := "" for _, name := range pl["others"] { - str += " " + name + "\n" + if len(name) > 3 { + str += name[4:] + "\n" // drop dns. prefix caddy adds + } else { + str += name + "\n" + } } return str