├── caddyfile ├── testdata │ ├── import_test1.txt │ ├── import_test2.txt │ ├── import_glob2.txt │ ├── import_glob1.txt │ └── import_glob0.txt ├── lexer.go ├── lexer_test.go ├── json_test.go ├── json.go ├── dispenser.go ├── dispenser_test.go ├── parse.go └── parse_test.go ├── go.mod ├── .gitignore ├── go.sum ├── onevent ├── hook │ ├── config.go │ ├── hook.go │ └── hook_test.go ├── on.go └── on_test.go ├── sigtrap_nonposix.go ├── rlimit_nonposix.go ├── assets_test.go ├── rlimit_posix.go ├── .gitattributes ├── assets.go ├── sigtrap_posix.go ├── sigtrap.go ├── commands.go ├── controller.go ├── caddy_test.go ├── upgrade.go ├── commands_test.go ├── README.md ├── LICENSE.txt ├── plugins.go └── caddy.go /caddyfile/testdata/import_test1.txt: -------------------------------------------------------------------------------- 1 | dir2 arg1 arg2 2 | dir3 -------------------------------------------------------------------------------- /caddyfile/testdata/import_test2.txt: -------------------------------------------------------------------------------- 1 | host1 { 2 | dir1 3 | dir2 arg1 4 | } -------------------------------------------------------------------------------- /caddyfile/testdata/import_glob2.txt: -------------------------------------------------------------------------------- 1 | glob2.host0 { 2 | dir2 arg1 3 | } 4 | -------------------------------------------------------------------------------- /caddyfile/testdata/import_glob1.txt: -------------------------------------------------------------------------------- 1 | glob1.host0 { 2 | dir1 3 | dir2 arg1 4 | } 5 | -------------------------------------------------------------------------------- /caddyfile/testdata/import_glob0.txt: -------------------------------------------------------------------------------- 1 | glob0.host0 { 2 | dir2 arg1 3 | } 4 | 5 | glob0.host1 { 6 | } 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/coredns/caddy 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 7 | github.com/google/uuid v1.1.1 8 | ) 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | _gitignore/ 4 | Vagrantfile 5 | .vagrant/ 6 | /.idea 7 | 8 | dist/builds/ 9 | dist/release/ 10 | 11 | error.log 12 | access.log 13 | 14 | /*.conf 15 | Caddyfile 16 | !caddyfile/ 17 | 18 | og_static/ 19 | 20 | .vscode/ 21 | 22 | *.bat 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= 2 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 3 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 4 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | -------------------------------------------------------------------------------- /onevent/hook/config.go: -------------------------------------------------------------------------------- 1 | package hook 2 | 3 | import ( 4 | "github.com/coredns/caddy" 5 | ) 6 | 7 | // Config describes how Hook should be configured and used. 8 | type Config struct { 9 | ID string 10 | Event caddy.EventName 11 | Command string 12 | Args []string 13 | } 14 | 15 | // SupportedEvents is a map of supported events. 16 | var SupportedEvents = map[string]caddy.EventName{ 17 | "startup": caddy.InstanceStartupEvent, 18 | "shutdown": caddy.ShutdownEvent, 19 | "certrenew": caddy.CertRenewEvent, 20 | } 21 | -------------------------------------------------------------------------------- /sigtrap_nonposix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build windows plan9 nacl js 16 | 17 | package caddy 18 | 19 | func trapSignalsPosix() {} 20 | -------------------------------------------------------------------------------- /rlimit_nonposix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build windows plan9 nacl js 16 | 17 | package caddy 18 | 19 | // checkFdlimit issues a warning if the OS limit for 20 | // max file descriptors is below a recommended minimum. 21 | func checkFdlimit() { 22 | } 23 | -------------------------------------------------------------------------------- /onevent/hook/hook.go: -------------------------------------------------------------------------------- 1 | package hook 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | 9 | "github.com/coredns/caddy" 10 | ) 11 | 12 | // Hook executes a command. 13 | func (cfg *Config) Hook(event caddy.EventName, info interface{}) error { 14 | if event != cfg.Event { 15 | return nil 16 | } 17 | 18 | nonblock := false 19 | if len(cfg.Args) >= 1 && cfg.Args[len(cfg.Args)-1] == "&" { 20 | // Run command in background; non-blocking 21 | nonblock = true 22 | cfg.Args = cfg.Args[:len(cfg.Args)-1] 23 | } 24 | 25 | // Execute command. 26 | cmd := exec.Command(cfg.Command, cfg.Args...) 27 | cmd.Stdin = os.Stdin 28 | cmd.Stdout = os.Stdout 29 | cmd.Stderr = os.Stderr 30 | if nonblock { 31 | log.Printf("[INFO] Nonblocking Command \"%s %s\" with ID %s", cfg.Command, strings.Join(cfg.Args, " "), cfg.ID) 32 | return cmd.Start() 33 | } 34 | log.Printf("[INFO] Blocking Command \"%s %s\" with ID %s", cfg.Command, strings.Join(cfg.Args, " "), cfg.ID) 35 | err := cmd.Run() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /assets_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "os" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestAssetsPath(t *testing.T) { 24 | if actual := AssetsPath(); !strings.HasSuffix(actual, ".caddy") { 25 | t.Errorf("Expected path to be a .caddy folder, got: %v", actual) 26 | } 27 | 28 | err := os.Setenv("CADDYPATH", "testpath") 29 | if err != nil { 30 | t.Error("Could not set CADDYPATH") 31 | } 32 | if actual, expected := AssetsPath(), "testpath"; actual != expected { 33 | t.Errorf("Expected path to be %v, got: %v", expected, actual) 34 | } 35 | err = os.Setenv("CADDYPATH", "") 36 | if err != nil { 37 | t.Error("Could not set CADDYPATH") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /rlimit_posix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build !windows,!plan9,!nacl,!js 16 | 17 | package caddy 18 | 19 | import ( 20 | "fmt" 21 | "syscall" 22 | ) 23 | 24 | // checkFdlimit issues a warning if the OS limit for 25 | // max file descriptors is below a recommended minimum. 26 | func checkFdlimit() { 27 | const min = 8192 28 | 29 | // Warn if ulimit is too low for production sites 30 | rlimit := &syscall.Rlimit{} 31 | err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimit) 32 | if err == nil && rlimit.Cur < min { 33 | fmt.Printf("WARNING: File descriptor limit %d is too low for production servers. "+ 34 | "At least %d is recommended. Fix with `ulimit -n %d`.\n", rlimit.Cur, min, min) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # shell scripts should not use tabs to indent! 2 | *.bash text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 3 | *.sh text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 4 | 5 | # files for systemd (shell-similar) 6 | *.path text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 7 | *.service text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 8 | *.timer text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 9 | 10 | # go fmt will enforce this, but in case a user has not called "go fmt" allow GIT to catch this: 11 | *.go text eol=lf core.whitespace whitespace=indent-with-non-tab,trailing-space,tabwidth=4 12 | go.mod text eol=lf 13 | go.sum text eol=lf 14 | 15 | *.txt text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 16 | *.tpl text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 17 | *.htm text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 18 | *.html text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 19 | *.md text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 20 | *.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2 21 | .git* text eol=auto core.whitespace whitespace=trailing-space 22 | -------------------------------------------------------------------------------- /assets.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "os" 19 | "path/filepath" 20 | "runtime" 21 | ) 22 | 23 | // AssetsPath returns the path to the folder 24 | // where the application may store data. If 25 | // CADDYPATH env variable is set, that value 26 | // is used. Otherwise, the path is the result 27 | // of evaluating "$HOME/.caddy". 28 | func AssetsPath() string { 29 | if caddyPath := os.Getenv("CADDYPATH"); caddyPath != "" { 30 | return caddyPath 31 | } 32 | return filepath.Join(userHomeDir(), ".caddy") 33 | } 34 | 35 | // userHomeDir returns the user's home directory according to 36 | // environment variables. 37 | // 38 | // Credit: http://stackoverflow.com/a/7922977/1048862 39 | func userHomeDir() string { 40 | if runtime.GOOS == "windows" { 41 | home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") 42 | if home == "" { 43 | home = os.Getenv("USERPROFILE") 44 | } 45 | return home 46 | } 47 | return os.Getenv("HOME") 48 | } 49 | -------------------------------------------------------------------------------- /onevent/on.go: -------------------------------------------------------------------------------- 1 | package onevent 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/coredns/caddy" 7 | "github.com/coredns/caddy/onevent/hook" 8 | "github.com/google/uuid" 9 | ) 10 | 11 | func init() { 12 | // Register Directive. 13 | caddy.RegisterPlugin("on", caddy.Plugin{Action: setup}) 14 | } 15 | 16 | func setup(c *caddy.Controller) error { 17 | config, err := onParse(c) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | // Register Event Hooks. 23 | err = c.OncePerServerBlock(func() error { 24 | for _, cfg := range config { 25 | caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook) 26 | } 27 | return nil 28 | }) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func onParse(c *caddy.Controller) ([]*hook.Config, error) { 37 | var config []*hook.Config 38 | 39 | for c.Next() { 40 | cfg := new(hook.Config) 41 | 42 | if !c.NextArg() { 43 | return config, c.ArgErr() 44 | } 45 | 46 | // Configure Event. 47 | event, ok := hook.SupportedEvents[strings.ToLower(c.Val())] 48 | if !ok { 49 | return config, c.Errf("Wrong event name or event not supported: '%s'", c.Val()) 50 | } 51 | cfg.Event = event 52 | 53 | // Assign an unique ID. 54 | cfg.ID = uuid.New().String() 55 | 56 | args := c.RemainingArgs() 57 | 58 | // Extract command and arguments. 59 | command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " ")) 60 | if err != nil { 61 | return config, c.Err(err.Error()) 62 | } 63 | 64 | cfg.Command = command 65 | cfg.Args = args 66 | 67 | config = append(config, cfg) 68 | } 69 | 70 | return config, nil 71 | } 72 | -------------------------------------------------------------------------------- /onevent/hook/hook_test.go: -------------------------------------------------------------------------------- 1 | package hook 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strconv" 7 | "testing" 8 | "time" 9 | 10 | "github.com/coredns/caddy" 11 | "github.com/google/uuid" 12 | ) 13 | 14 | func TestHook(t *testing.T) { 15 | tempDirPath := os.TempDir() 16 | 17 | testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_command") 18 | defer func() { 19 | // clean up after non-blocking startup function quits 20 | time.Sleep(500 * time.Millisecond) 21 | os.RemoveAll(testDir) 22 | }() 23 | osSensitiveTestDir := filepath.FromSlash(testDir) 24 | os.RemoveAll(osSensitiveTestDir) // start with a clean slate 25 | 26 | tests := []struct { 27 | name string 28 | event caddy.EventName 29 | command string 30 | args []string 31 | shouldErr bool 32 | shouldRemoveErr bool 33 | }{ 34 | {name: "blocking", event: caddy.InstanceStartupEvent, command: "mkdir", args: []string{osSensitiveTestDir}, shouldErr: false, shouldRemoveErr: false}, 35 | {name: "nonBlocking", event: caddy.ShutdownEvent, command: "mkdir", args: []string{osSensitiveTestDir, "&"}, shouldErr: false, shouldRemoveErr: true}, 36 | {name: "nonBlocking2", event: caddy.ShutdownEvent, command: "echo", args: []string{"&"}, shouldErr: false, shouldRemoveErr: true}, 37 | {name: "nonExistent", event: caddy.CertRenewEvent, command: strconv.Itoa(int(time.Now().UnixNano())), shouldErr: true, shouldRemoveErr: true}, 38 | } 39 | 40 | for _, test := range tests { 41 | t.Run(test.name, func(t *testing.T) { 42 | cfg := new(Config) 43 | cfg.ID = uuid.New().String() 44 | cfg.Event = test.event 45 | cfg.Command = test.command 46 | cfg.Args = test.args 47 | 48 | err := cfg.Hook(test.event, nil) 49 | if err == nil && test.shouldErr { 50 | t.Error("Test didn't error, but it should have") 51 | } else if err != nil && !test.shouldErr { 52 | t.Errorf("Test errored, but it shouldn't have; got '%v'", err) 53 | } 54 | 55 | err = os.Remove(osSensitiveTestDir) 56 | if err != nil && !test.shouldRemoveErr { 57 | t.Errorf("Test received an error of:\n%v", err) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /onevent/on_test.go: -------------------------------------------------------------------------------- 1 | package onevent 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/coredns/caddy" 7 | "github.com/coredns/caddy/onevent/hook" 8 | ) 9 | 10 | func TestSetup(t *testing.T) { 11 | tests := []struct { 12 | name string 13 | input string 14 | shouldErr bool 15 | }{ 16 | {name: "noInput", input: "on", shouldErr: true}, 17 | {name: "nonExistent", input: "on xyz cmd arg", shouldErr: true}, 18 | {name: "startup", input: "on startup cmd arg", shouldErr: false}, 19 | {name: "shutdown", input: "on shutdown cmd arg &", shouldErr: false}, 20 | {name: "certrenew", input: "on certrenew cmd arg", shouldErr: false}, 21 | } 22 | 23 | for _, test := range tests { 24 | t.Run(test.name, func(t *testing.T) { 25 | c := caddy.NewTestController("http", test.input) 26 | c.Key = test.name 27 | 28 | err := setup(c) 29 | 30 | if err == nil && test.shouldErr { 31 | t.Error("Test didn't error, but it should have") 32 | } else if err != nil && !test.shouldErr { 33 | t.Errorf("Test errored, but it shouldn't have; got '%v'", err) 34 | } 35 | }) 36 | } 37 | } 38 | 39 | func TestCommandParse(t *testing.T) { 40 | tests := []struct { 41 | name string 42 | input string 43 | shouldErr bool 44 | config hook.Config 45 | }{ 46 | {name: "noInput", input: `on`, shouldErr: true}, 47 | {name: "nonExistent", input: "on xyz cmd arg", shouldErr: true}, 48 | {name: "startup", input: `on startup cmd arg1 arg2`, shouldErr: false, config: hook.Config{Event: caddy.InstanceStartupEvent, Command: "cmd", Args: []string{"arg1", "arg2"}}}, 49 | {name: "shutdown", input: `on shutdown cmd arg1 arg2 &`, shouldErr: false, config: hook.Config{Event: caddy.ShutdownEvent, Command: "cmd", Args: []string{"arg1", "arg2", "&"}}}, 50 | {name: "certrenew", input: `on certrenew cmd arg1 arg2`, shouldErr: false, config: hook.Config{Event: caddy.CertRenewEvent, Command: "cmd", Args: []string{"arg1", "arg2"}}}, 51 | } 52 | 53 | for _, test := range tests { 54 | t.Run(test.name, func(t *testing.T) { 55 | config, err := onParse(caddy.NewTestController("http", test.input)) 56 | 57 | if err == nil && test.shouldErr { 58 | t.Error("Test didn't error, but it should have") 59 | } else if err != nil && !test.shouldErr { 60 | t.Errorf("Test errored, but it shouldn't have; got '%v'", err) 61 | } 62 | 63 | for _, cfg := range config { 64 | if cfg.Event != test.config.Event { 65 | t.Errorf("Expected event %s; got %s", test.config.Event, cfg.Event) 66 | } 67 | 68 | if cfg.Command != test.config.Command { 69 | t.Errorf("Expected command %s; got %s", test.config.Command, cfg.Command) 70 | } 71 | 72 | for i, arg := range cfg.Args { 73 | if arg != test.config.Args[i] { 74 | t.Errorf("Expected arg in position %d to be %s, got %s", i, test.config.Args[i], arg) 75 | } 76 | } 77 | 78 | } 79 | }) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /sigtrap_posix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build !windows,!plan9,!nacl,!js 16 | 17 | package caddy 18 | 19 | import ( 20 | "log" 21 | "os" 22 | "os/signal" 23 | "syscall" 24 | ) 25 | 26 | // trapSignalsPosix captures POSIX-only signals. 27 | func trapSignalsPosix() { 28 | go func() { 29 | sigchan := make(chan os.Signal, 1) 30 | signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2) 31 | 32 | for sig := range sigchan { 33 | switch sig { 34 | case syscall.SIGQUIT: 35 | log.Println("[INFO] SIGQUIT: Quitting process immediately") 36 | for _, f := range OnProcessExit { 37 | f() // only perform important cleanup actions 38 | } 39 | os.Exit(0) 40 | 41 | case syscall.SIGTERM: 42 | log.Println("[INFO] SIGTERM: Shutting down servers then terminating") 43 | exitCode := executeShutdownCallbacks("SIGTERM") 44 | for _, f := range OnProcessExit { 45 | f() // only perform important cleanup actions 46 | } 47 | err := Stop() 48 | if err != nil { 49 | log.Printf("[ERROR] SIGTERM stop: %v", err) 50 | exitCode = 3 51 | } 52 | 53 | os.Exit(exitCode) 54 | 55 | case syscall.SIGUSR1: 56 | log.Println("[INFO] SIGUSR1: Reloading") 57 | 58 | // Start with the existing Caddyfile 59 | caddyfileToUse, inst, err := getCurrentCaddyfile() 60 | if err != nil { 61 | log.Printf("[ERROR] SIGUSR1: %v", err) 62 | continue 63 | } 64 | if loaderUsed.loader == nil { 65 | // This also should never happen 66 | log.Println("[ERROR] SIGUSR1: no Caddyfile loader with which to reload Caddyfile") 67 | continue 68 | } 69 | 70 | // Load the updated Caddyfile 71 | newCaddyfile, err := loaderUsed.loader.Load(inst.serverType) 72 | if err != nil { 73 | log.Printf("[ERROR] SIGUSR1: loading updated Caddyfile: %v", err) 74 | continue 75 | } 76 | if newCaddyfile != nil { 77 | caddyfileToUse = newCaddyfile 78 | } 79 | 80 | // Backup old event hooks 81 | oldEventHooks := cloneEventHooks() 82 | 83 | // Purge the old event hooks 84 | purgeEventHooks() 85 | 86 | // Kick off the restart; our work is done 87 | EmitEvent(InstanceRestartEvent, nil) 88 | _, err = inst.Restart(caddyfileToUse) 89 | if err != nil { 90 | restoreEventHooks(oldEventHooks) 91 | 92 | log.Printf("[ERROR] SIGUSR1: %v", err) 93 | } 94 | 95 | case syscall.SIGUSR2: 96 | log.Println("[INFO] SIGUSR2: Upgrading") 97 | if err := Upgrade(); err != nil { 98 | log.Printf("[ERROR] SIGUSR2: upgrading: %v", err) 99 | } 100 | 101 | case syscall.SIGHUP: 102 | // ignore; this signal is sometimes sent outside of the user's control 103 | } 104 | } 105 | }() 106 | } 107 | -------------------------------------------------------------------------------- /sigtrap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "log" 19 | "os" 20 | "os/signal" 21 | "sync" 22 | ) 23 | 24 | // TrapSignals create signal handlers for all applicable signals for this 25 | // system. If your Go program uses signals, this is a rather invasive 26 | // function; best to implement them yourself in that case. Signals are not 27 | // required for the caddy package to function properly, but this is a 28 | // convenient way to allow the user to control this part of your program. 29 | func TrapSignals() { 30 | trapSignalsCrossPlatform() 31 | trapSignalsPosix() 32 | } 33 | 34 | // trapSignalsCrossPlatform captures SIGINT, which triggers forceful 35 | // shutdown that executes shutdown callbacks first. A second interrupt 36 | // signal will exit the process immediately. 37 | func trapSignalsCrossPlatform() { 38 | go func() { 39 | shutdown := make(chan os.Signal, 1) 40 | signal.Notify(shutdown, os.Interrupt) 41 | 42 | for i := 0; true; i++ { 43 | <-shutdown 44 | 45 | if i > 0 { 46 | log.Println("[INFO] SIGINT: Force quit") 47 | for _, f := range OnProcessExit { 48 | f() // important cleanup actions only 49 | } 50 | os.Exit(2) 51 | } 52 | 53 | log.Println("[INFO] SIGINT: Shutting down") 54 | 55 | // important cleanup actions before shutdown callbacks 56 | for _, f := range OnProcessExit { 57 | f() 58 | } 59 | 60 | go func() { 61 | os.Exit(executeShutdownCallbacks("SIGINT")) 62 | }() 63 | } 64 | }() 65 | } 66 | 67 | // executeShutdownCallbacks executes the shutdown callbacks as initiated 68 | // by signame. It logs any errors and returns the recommended exit status. 69 | // This function is idempotent; subsequent invocations always return 0. 70 | func executeShutdownCallbacks(signame string) (exitCode int) { 71 | shutdownCallbacksOnce.Do(func() { 72 | // execute third-party shutdown hooks 73 | EmitEvent(ShutdownEvent, signame) 74 | 75 | errs := allShutdownCallbacks() 76 | if len(errs) > 0 { 77 | for _, err := range errs { 78 | log.Printf("[ERROR] %s shutdown: %v", signame, err) 79 | } 80 | exitCode = 4 81 | } 82 | }) 83 | return 84 | } 85 | 86 | // allShutdownCallbacks executes all the shutdown callbacks 87 | // for all the instances, and returns all the errors generated 88 | // during their execution. An error executing one shutdown 89 | // callback does not stop execution of others. Only one shutdown 90 | // callback is executed at a time. 91 | func allShutdownCallbacks() []error { 92 | var errs []error 93 | instancesMu.Lock() 94 | for _, inst := range instances { 95 | errs = append(errs, inst.ShutdownCallbacks()...) 96 | } 97 | instancesMu.Unlock() 98 | return errs 99 | } 100 | 101 | // shutdownCallbacksOnce ensures that shutdown callbacks 102 | // for all instances are only executed once. 103 | var shutdownCallbacksOnce sync.Once 104 | -------------------------------------------------------------------------------- /caddyfile/lexer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "bufio" 19 | "io" 20 | "unicode" 21 | ) 22 | 23 | type ( 24 | // lexer is a utility which can get values, token by 25 | // token, from a Reader. A token is a word, and tokens 26 | // are separated by whitespace. A word can be enclosed 27 | // in quotes if it contains whitespace. 28 | lexer struct { 29 | reader *bufio.Reader 30 | token Token 31 | line int 32 | } 33 | 34 | // Token represents a single parsable unit. 35 | Token struct { 36 | File string 37 | Line int 38 | Text string 39 | } 40 | ) 41 | 42 | // load prepares the lexer to scan an input for tokens. 43 | // It discards any leading byte order mark. 44 | func (l *lexer) load(input io.Reader) error { 45 | l.reader = bufio.NewReader(input) 46 | l.line = 1 47 | 48 | // discard byte order mark, if present 49 | firstCh, _, err := l.reader.ReadRune() 50 | if err != nil { 51 | if err == io.EOF { 52 | return nil 53 | } 54 | return err 55 | } 56 | if firstCh != 0xFEFF { 57 | err := l.reader.UnreadRune() 58 | if err != nil { 59 | return err 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // next loads the next token into the lexer. 67 | // A token is delimited by whitespace, unless 68 | // the token starts with a quotes character (") 69 | // in which case the token goes until the closing 70 | // quotes (the enclosing quotes are not included). 71 | // Inside quoted strings, quotes may be escaped 72 | // with a preceding \ character. No other chars 73 | // may be escaped. The rest of the line is skipped 74 | // if a "#" character is read in. Returns true if 75 | // a token was loaded; false otherwise. 76 | func (l *lexer) next() bool { 77 | var val []rune 78 | var comment, quoted, escaped bool 79 | 80 | makeToken := func() bool { 81 | l.token.Text = string(val) 82 | return true 83 | } 84 | 85 | for { 86 | ch, _, err := l.reader.ReadRune() 87 | if err != nil { 88 | if len(val) > 0 { 89 | return makeToken() 90 | } 91 | if err == io.EOF { 92 | return false 93 | } 94 | panic(err) 95 | } 96 | 97 | if quoted { 98 | if !escaped { 99 | if ch == '\\' { 100 | escaped = true 101 | continue 102 | } else if ch == '"' { 103 | quoted = false 104 | return makeToken() 105 | } 106 | } 107 | if ch == '\n' { 108 | l.line++ 109 | } 110 | if escaped { 111 | // only escape quotes 112 | if ch != '"' { 113 | val = append(val, '\\') 114 | } 115 | } 116 | val = append(val, ch) 117 | escaped = false 118 | continue 119 | } 120 | 121 | if unicode.IsSpace(ch) { 122 | if ch == '\r' { 123 | continue 124 | } 125 | if ch == '\n' { 126 | l.line++ 127 | comment = false 128 | } 129 | if len(val) > 0 { 130 | return makeToken() 131 | } 132 | continue 133 | } 134 | 135 | if ch == '#' { 136 | comment = true 137 | } 138 | 139 | if comment { 140 | continue 141 | } 142 | 143 | if len(val) == 0 { 144 | l.token = Token{Line: l.line} 145 | if ch == '"' { 146 | quoted = true 147 | continue 148 | } 149 | } 150 | 151 | val = append(val, ch) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /commands.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "errors" 19 | "runtime" 20 | "unicode" 21 | 22 | "github.com/flynn/go-shlex" 23 | ) 24 | 25 | var runtimeGoos = runtime.GOOS 26 | 27 | // SplitCommandAndArgs takes a command string and parses it shell-style into the 28 | // command and its separate arguments. 29 | func SplitCommandAndArgs(command string) (cmd string, args []string, err error) { 30 | var parts []string 31 | 32 | if runtimeGoos == "windows" { 33 | parts = parseWindowsCommand(command) // parse it Windows-style 34 | } else { 35 | parts, err = parseUnixCommand(command) // parse it Unix-style 36 | if err != nil { 37 | err = errors.New("error parsing command: " + err.Error()) 38 | return 39 | } 40 | } 41 | 42 | if len(parts) == 0 { 43 | err = errors.New("no command contained in '" + command + "'") 44 | return 45 | } 46 | 47 | cmd = parts[0] 48 | if len(parts) > 1 { 49 | args = parts[1:] 50 | } 51 | 52 | return 53 | } 54 | 55 | // parseUnixCommand parses a unix style command line and returns the 56 | // command and its arguments or an error 57 | func parseUnixCommand(cmd string) ([]string, error) { 58 | return shlex.Split(cmd) 59 | } 60 | 61 | // parseWindowsCommand parses windows command lines and 62 | // returns the command and the arguments as an array. It 63 | // should be able to parse commonly used command lines. 64 | // Only basic syntax is supported: 65 | // - spaces in double quotes are not token delimiters 66 | // - double quotes are escaped by either backspace or another double quote 67 | // - except for the above case backspaces are path separators (not special) 68 | // 69 | // Many sources point out that escaping quotes using backslash can be unsafe. 70 | // Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 ) 71 | // 72 | // This function has to be used on Windows instead 73 | // of the shlex package because this function treats backslash 74 | // characters properly. 75 | func parseWindowsCommand(cmd string) []string { 76 | const backslash = '\\' 77 | const quote = '"' 78 | 79 | var parts []string 80 | var part string 81 | var inQuotes bool 82 | var lastRune rune 83 | 84 | for i, ch := range cmd { 85 | 86 | if i != 0 { 87 | lastRune = rune(cmd[i-1]) 88 | } 89 | 90 | if ch == backslash { 91 | // put it in the part - for now we don't know if it's an 92 | // escaping char or path separator 93 | part += string(ch) 94 | continue 95 | } 96 | 97 | if ch == quote { 98 | if lastRune == backslash { 99 | // remove the backslash from the part and add the escaped quote instead 100 | part = part[:len(part)-1] 101 | part += string(ch) 102 | continue 103 | } 104 | 105 | if lastRune == quote { 106 | // revert the last change of the inQuotes state 107 | // it was an escaping quote 108 | inQuotes = !inQuotes 109 | part += string(ch) 110 | continue 111 | } 112 | 113 | // normal escaping quotes 114 | inQuotes = !inQuotes 115 | continue 116 | 117 | } 118 | 119 | if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 { 120 | parts = append(parts, part) 121 | part = "" 122 | continue 123 | } 124 | 125 | part += string(ch) 126 | } 127 | 128 | if len(part) > 0 { 129 | parts = append(parts, part) 130 | } 131 | 132 | return parts 133 | } 134 | -------------------------------------------------------------------------------- /caddyfile/lexer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "log" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | type lexerTestCase struct { 24 | input string 25 | expected []Token 26 | } 27 | 28 | func TestLexer(t *testing.T) { 29 | testCases := []lexerTestCase{ 30 | { 31 | input: `host:123`, 32 | expected: []Token{ 33 | {Line: 1, Text: "host:123"}, 34 | }, 35 | }, 36 | { 37 | input: `host:123 38 | 39 | directive`, 40 | expected: []Token{ 41 | {Line: 1, Text: "host:123"}, 42 | {Line: 3, Text: "directive"}, 43 | }, 44 | }, 45 | { 46 | input: `host:123 { 47 | directive 48 | }`, 49 | expected: []Token{ 50 | {Line: 1, Text: "host:123"}, 51 | {Line: 1, Text: "{"}, 52 | {Line: 2, Text: "directive"}, 53 | {Line: 3, Text: "}"}, 54 | }, 55 | }, 56 | { 57 | input: `host:123 { directive }`, 58 | expected: []Token{ 59 | {Line: 1, Text: "host:123"}, 60 | {Line: 1, Text: "{"}, 61 | {Line: 1, Text: "directive"}, 62 | {Line: 1, Text: "}"}, 63 | }, 64 | }, 65 | { 66 | input: `host:123 { 67 | #comment 68 | directive 69 | # comment 70 | foobar # another comment 71 | }`, 72 | expected: []Token{ 73 | {Line: 1, Text: "host:123"}, 74 | {Line: 1, Text: "{"}, 75 | {Line: 3, Text: "directive"}, 76 | {Line: 5, Text: "foobar"}, 77 | {Line: 6, Text: "}"}, 78 | }, 79 | }, 80 | { 81 | input: `a "quoted value" b 82 | foobar`, 83 | expected: []Token{ 84 | {Line: 1, Text: "a"}, 85 | {Line: 1, Text: "quoted value"}, 86 | {Line: 1, Text: "b"}, 87 | {Line: 2, Text: "foobar"}, 88 | }, 89 | }, 90 | { 91 | input: `A "quoted \"value\" inside" B`, 92 | expected: []Token{ 93 | {Line: 1, Text: "A"}, 94 | {Line: 1, Text: `quoted "value" inside`}, 95 | {Line: 1, Text: "B"}, 96 | }, 97 | }, 98 | { 99 | input: `"don't\escape"`, 100 | expected: []Token{ 101 | {Line: 1, Text: `don't\escape`}, 102 | }, 103 | }, 104 | { 105 | input: `"don't\\escape"`, 106 | expected: []Token{ 107 | {Line: 1, Text: `don't\\escape`}, 108 | }, 109 | }, 110 | { 111 | input: `A "quoted value with line 112 | break inside" { 113 | foobar 114 | }`, 115 | expected: []Token{ 116 | {Line: 1, Text: "A"}, 117 | {Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"}, 118 | {Line: 2, Text: "{"}, 119 | {Line: 3, Text: "foobar"}, 120 | {Line: 4, Text: "}"}, 121 | }, 122 | }, 123 | { 124 | input: `"C:\php\php-cgi.exe"`, 125 | expected: []Token{ 126 | {Line: 1, Text: `C:\php\php-cgi.exe`}, 127 | }, 128 | }, 129 | { 130 | input: `empty "" string`, 131 | expected: []Token{ 132 | {Line: 1, Text: `empty`}, 133 | {Line: 1, Text: ``}, 134 | {Line: 1, Text: `string`}, 135 | }, 136 | }, 137 | { 138 | input: "skip those\r\nCR characters", 139 | expected: []Token{ 140 | {Line: 1, Text: "skip"}, 141 | {Line: 1, Text: "those"}, 142 | {Line: 2, Text: "CR"}, 143 | {Line: 2, Text: "characters"}, 144 | }, 145 | }, 146 | { 147 | input: "\xEF\xBB\xBF:8080", // test with leading byte order mark 148 | expected: []Token{ 149 | {Line: 1, Text: ":8080"}, 150 | }, 151 | }, 152 | } 153 | 154 | for i, testCase := range testCases { 155 | actual := tokenize(testCase.input) 156 | lexerCompare(t, i, testCase.expected, actual) 157 | } 158 | } 159 | 160 | func tokenize(input string) (tokens []Token) { 161 | l := lexer{} 162 | if err := l.load(strings.NewReader(input)); err != nil { 163 | log.Printf("[ERROR] load failed: %v", err) 164 | } 165 | for l.next() { 166 | tokens = append(tokens, l.token) 167 | } 168 | return 169 | } 170 | 171 | func lexerCompare(t *testing.T, n int, expected, actual []Token) { 172 | if len(expected) != len(actual) { 173 | t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual)) 174 | } 175 | 176 | for i := 0; i < len(actual) && i < len(expected); i++ { 177 | if actual[i].Line != expected[i].Line { 178 | t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d", 179 | n, i, expected[i].Text, expected[i].Line, actual[i].Line) 180 | break 181 | } 182 | if actual[i].Text != expected[i].Text { 183 | t.Errorf("Test case %d token %d: expected text '%s' but was '%s'", 184 | n, i, expected[i].Text, actual[i].Text) 185 | break 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /caddyfile/json_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import "testing" 18 | 19 | var tests = []struct { 20 | caddyfile, json string 21 | }{ 22 | { // 0 23 | caddyfile: `foo { 24 | root /bar 25 | }`, 26 | json: `[{"keys":["foo"],"body":[["root","/bar"]]}]`, 27 | }, 28 | { // 1 29 | caddyfile: `host1, host2 { 30 | dir { 31 | def 32 | } 33 | }`, 34 | json: `[{"keys":["host1","host2"],"body":[["dir",[["def"]]]]}]`, 35 | }, 36 | { // 2 37 | caddyfile: `host1, host2 { 38 | dir abc { 39 | def ghi 40 | jkl 41 | } 42 | }`, 43 | json: `[{"keys":["host1","host2"],"body":[["dir","abc",[["def","ghi"],["jkl"]]]]}]`, 44 | }, 45 | { // 3 46 | caddyfile: `host1:1234, host2:5678 { 47 | dir abc { 48 | } 49 | }`, 50 | json: `[{"keys":["host1:1234","host2:5678"],"body":[["dir","abc",[]]]}]`, 51 | }, 52 | { // 4 53 | caddyfile: `host { 54 | foo "bar baz" 55 | }`, 56 | json: `[{"keys":["host"],"body":[["foo","bar baz"]]}]`, 57 | }, 58 | { // 5 59 | caddyfile: `host, host:80 { 60 | foo "bar \"baz\"" 61 | }`, 62 | json: `[{"keys":["host","host:80"],"body":[["foo","bar \"baz\""]]}]`, 63 | }, 64 | { // 6 65 | caddyfile: `host { 66 | foo "bar 67 | baz" 68 | }`, 69 | json: `[{"keys":["host"],"body":[["foo","bar\nbaz"]]}]`, 70 | }, 71 | { // 7 72 | caddyfile: `host { 73 | dir 123 4.56 true 74 | }`, 75 | json: `[{"keys":["host"],"body":[["dir","123","4.56","true"]]}]`, // NOTE: I guess we assume numbers and booleans should be encoded as strings...? 76 | }, 77 | { // 8 78 | caddyfile: `http://host, https://host { 79 | }`, 80 | json: `[{"keys":["http://host","https://host"],"body":[]}]`, // hosts in JSON are always host:port format (if port is specified), for consistency 81 | }, 82 | { // 9 83 | caddyfile: `host { 84 | dir1 a b 85 | dir2 c d 86 | }`, 87 | json: `[{"keys":["host"],"body":[["dir1","a","b"],["dir2","c","d"]]}]`, 88 | }, 89 | { // 10 90 | caddyfile: `host { 91 | dir a b 92 | dir c d 93 | }`, 94 | json: `[{"keys":["host"],"body":[["dir","a","b"],["dir","c","d"]]}]`, 95 | }, 96 | { // 11 97 | caddyfile: `host { 98 | dir1 a b 99 | dir2 { 100 | c 101 | d 102 | } 103 | }`, 104 | json: `[{"keys":["host"],"body":[["dir1","a","b"],["dir2",[["c"],["d"]]]]}]`, 105 | }, 106 | { // 12 107 | caddyfile: `host1 { 108 | dir1 109 | } 110 | 111 | host2 { 112 | dir2 113 | }`, 114 | json: `[{"keys":["host1"],"body":[["dir1"]]},{"keys":["host2"],"body":[["dir2"]]}]`, 115 | }, 116 | } 117 | 118 | func TestToJSON(t *testing.T) { 119 | for i, test := range tests { 120 | output, err := ToJSON([]byte(test.caddyfile)) 121 | if err != nil { 122 | t.Errorf("Test %d: %v", i, err) 123 | } 124 | if string(output) != test.json { 125 | t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.json, string(output)) 126 | } 127 | } 128 | } 129 | 130 | func TestFromJSON(t *testing.T) { 131 | for i, test := range tests { 132 | output, err := FromJSON([]byte(test.json)) 133 | if err != nil { 134 | t.Errorf("Test %d: %v", i, err) 135 | } 136 | if string(output) != test.caddyfile { 137 | t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.caddyfile, string(output)) 138 | } 139 | } 140 | } 141 | 142 | // TODO: Will these tests come in handy somewhere else? 143 | /* 144 | func TestStandardizeAddress(t *testing.T) { 145 | // host:https should be converted to https://host 146 | output, err := ToJSON([]byte(`host:https`)) 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | if expected, actual := `[{"keys":["https://host"],"body":[]}]`, string(output); expected != actual { 151 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual) 152 | } 153 | 154 | output, err = FromJSON([]byte(`[{"keys":["https://host"],"body":[]}]`)) 155 | if err != nil { 156 | t.Fatal(err) 157 | } 158 | if expected, actual := "https://host {\n}", string(output); expected != actual { 159 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual) 160 | } 161 | 162 | // host: should be converted to just host 163 | output, err = ToJSON([]byte(`host:`)) 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | if expected, actual := `[{"keys":["host"],"body":[]}]`, string(output); expected != actual { 168 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual) 169 | } 170 | output, err = FromJSON([]byte(`[{"keys":["host:"],"body":[]}]`)) 171 | if err != nil { 172 | t.Fatal(err) 173 | } 174 | if expected, actual := "host {\n}", string(output); expected != actual { 175 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual) 176 | } 177 | } 178 | */ 179 | -------------------------------------------------------------------------------- /caddyfile/json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "fmt" 21 | "sort" 22 | "strconv" 23 | "strings" 24 | ) 25 | 26 | const filename = "Caddyfile" 27 | 28 | // ToJSON converts caddyfile to its JSON representation. 29 | func ToJSON(caddyfile []byte) ([]byte, error) { 30 | var j EncodedCaddyfile 31 | 32 | serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | for _, sb := range serverBlocks { 38 | block := EncodedServerBlock{ 39 | Keys: sb.Keys, 40 | Body: [][]interface{}{}, 41 | } 42 | 43 | // Extract directives deterministically by sorting them 44 | var directives = make([]string, len(sb.Tokens)) 45 | for dir := range sb.Tokens { 46 | directives = append(directives, dir) 47 | } 48 | sort.Strings(directives) 49 | 50 | // Convert each directive's tokens into our JSON structure 51 | for _, dir := range directives { 52 | disp := NewDispenserTokens(filename, sb.Tokens[dir]) 53 | for disp.Next() { 54 | block.Body = append(block.Body, constructLine(&disp)) 55 | } 56 | } 57 | 58 | // tack this block onto the end of the list 59 | j = append(j, block) 60 | } 61 | 62 | result, err := json.Marshal(j) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return result, nil 68 | } 69 | 70 | // constructLine transforms tokens into a JSON-encodable structure; 71 | // but only one line at a time, to be used at the top-level of 72 | // a server block only (where the first token on each line is a 73 | // directive) - not to be used at any other nesting level. 74 | func constructLine(d *Dispenser) []interface{} { 75 | var args []interface{} 76 | 77 | args = append(args, d.Val()) 78 | 79 | for d.NextArg() { 80 | if d.Val() == "{" { 81 | args = append(args, constructBlock(d)) 82 | continue 83 | } 84 | args = append(args, d.Val()) 85 | } 86 | 87 | return args 88 | } 89 | 90 | // constructBlock recursively processes tokens into a 91 | // JSON-encodable structure. To be used in a directive's 92 | // block. Goes to end of block. 93 | func constructBlock(d *Dispenser) [][]interface{} { 94 | block := [][]interface{}{} 95 | 96 | for d.Next() { 97 | if d.Val() == "}" { 98 | break 99 | } 100 | block = append(block, constructLine(d)) 101 | } 102 | 103 | return block 104 | } 105 | 106 | // FromJSON converts JSON-encoded jsonBytes to Caddyfile text 107 | func FromJSON(jsonBytes []byte) ([]byte, error) { 108 | var j EncodedCaddyfile 109 | var result string 110 | 111 | err := json.Unmarshal(jsonBytes, &j) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | for sbPos, sb := range j { 117 | if sbPos > 0 { 118 | result += "\n\n" 119 | } 120 | for i, key := range sb.Keys { 121 | if i > 0 { 122 | result += ", " 123 | } 124 | //result += standardizeScheme(key) 125 | result += key 126 | } 127 | result += jsonToText(sb.Body, 1) 128 | } 129 | 130 | return []byte(result), nil 131 | } 132 | 133 | // jsonToText recursively transforms a scope of JSON into plain 134 | // Caddyfile text. 135 | func jsonToText(scope interface{}, depth int) string { 136 | var result string 137 | 138 | switch val := scope.(type) { 139 | case string: 140 | if strings.ContainsAny(val, "\" \n\t\r") { 141 | result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"` 142 | } else { 143 | result += val 144 | } 145 | case int: 146 | result += strconv.Itoa(val) 147 | case float64: 148 | result += fmt.Sprintf("%v", val) 149 | case bool: 150 | result += fmt.Sprintf("%t", val) 151 | case [][]interface{}: 152 | result += " {\n" 153 | for _, arg := range val { 154 | result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n" 155 | } 156 | result += strings.Repeat("\t", depth-1) + "}" 157 | case []interface{}: 158 | for i, v := range val { 159 | if block, ok := v.([]interface{}); ok { 160 | result += "{\n" 161 | for _, arg := range block { 162 | result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n" 163 | } 164 | result += strings.Repeat("\t", depth-1) + "}" 165 | continue 166 | } 167 | result += jsonToText(v, depth) 168 | if i < len(val)-1 { 169 | result += " " 170 | } 171 | } 172 | } 173 | 174 | return result 175 | } 176 | 177 | // TODO: Will this function come in handy somewhere else? 178 | /* 179 | // standardizeScheme turns an address like host:https into https://host, 180 | // or "host:" into "host". 181 | func standardizeScheme(addr string) string { 182 | if hostname, port, err := net.SplitHostPort(addr); err == nil { 183 | if port == "http" || port == "https" { 184 | addr = port + "://" + hostname 185 | } 186 | } 187 | return strings.TrimSuffix(addr, ":") 188 | } 189 | */ 190 | 191 | // EncodedCaddyfile encapsulates a slice of EncodedServerBlocks. 192 | type EncodedCaddyfile []EncodedServerBlock 193 | 194 | // EncodedServerBlock represents a server block ripe for encoding. 195 | type EncodedServerBlock struct { 196 | Keys []string `json:"keys"` 197 | Body [][]interface{} `json:"body"` 198 | } 199 | -------------------------------------------------------------------------------- /controller.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "strings" 19 | 20 | "github.com/coredns/caddy/caddyfile" 21 | ) 22 | 23 | // Controller is given to the setup function of directives which 24 | // gives them access to be able to read tokens with which to 25 | // configure themselves. It also stores state for the setup 26 | // functions, can get the current context, and can be used to 27 | // identify a particular server block using the Key field. 28 | type Controller struct { 29 | caddyfile.Dispenser 30 | 31 | // The instance in which the setup is occurring 32 | instance *Instance 33 | 34 | // Key is the key from the top of the server block, usually 35 | // an address, hostname, or identifier of some sort. 36 | Key string 37 | 38 | // OncePerServerBlock is a function that executes f 39 | // exactly once per server block, no matter how many 40 | // hosts are associated with it. If it is the first 41 | // time, the function f is executed immediately 42 | // (not deferred) and may return an error which is 43 | // returned by OncePerServerBlock. 44 | OncePerServerBlock func(f func() error) error 45 | 46 | // ServerBlockIndex is the 0-based index of the 47 | // server block as it appeared in the input. 48 | ServerBlockIndex int 49 | 50 | // ServerBlockKeyIndex is the 0-based index of this 51 | // key as it appeared in the input at the head of the 52 | // server block. 53 | ServerBlockKeyIndex int 54 | 55 | // ServerBlockKeys is a list of keys that are 56 | // associated with this server block. All these 57 | // keys, consequently, share the same tokens. 58 | ServerBlockKeys []string 59 | 60 | // ServerBlockStorage is used by a directive's 61 | // setup function to persist state between all 62 | // the keys on a server block. 63 | ServerBlockStorage interface{} 64 | } 65 | 66 | // ServerType gets the name of the server type that is being set up. 67 | func (c *Controller) ServerType() string { 68 | return c.instance.serverType 69 | } 70 | 71 | // OnFirstStartup adds fn to the list of callback functions to execute 72 | // when the server is about to be started NOT as part of a restart. 73 | func (c *Controller) OnFirstStartup(fn func() error) { 74 | c.instance.OnFirstStartup = append(c.instance.OnFirstStartup, fn) 75 | } 76 | 77 | // OnStartup adds fn to the list of callback functions to execute 78 | // when the server is about to be started (including restarts). 79 | func (c *Controller) OnStartup(fn func() error) { 80 | c.instance.OnStartup = append(c.instance.OnStartup, fn) 81 | } 82 | 83 | // OnRestart adds fn to the list of callback functions to execute 84 | // when the server is about to be restarted. 85 | func (c *Controller) OnRestart(fn func() error) { 86 | c.instance.OnRestart = append(c.instance.OnRestart, fn) 87 | } 88 | 89 | // OnRestartFailed adds fn to the list of callback functions to execute 90 | // if the server failed to restart. 91 | func (c *Controller) OnRestartFailed(fn func() error) { 92 | c.instance.OnRestartFailed = append(c.instance.OnRestartFailed, fn) 93 | } 94 | 95 | // OnShutdown adds fn to the list of callback functions to execute 96 | // when the server is about to be shut down (including restarts). 97 | func (c *Controller) OnShutdown(fn func() error) { 98 | c.instance.OnShutdown = append(c.instance.OnShutdown, fn) 99 | } 100 | 101 | // OnFinalShutdown adds fn to the list of callback functions to execute 102 | // when the server is about to be shut down NOT as part of a restart. 103 | func (c *Controller) OnFinalShutdown(fn func() error) { 104 | c.instance.OnFinalShutdown = append(c.instance.OnFinalShutdown, fn) 105 | } 106 | 107 | // Context gets the context associated with the instance associated with c. 108 | func (c *Controller) Context() Context { 109 | return c.instance.context 110 | } 111 | 112 | // Get safely gets a value from the Instance's storage. 113 | func (c *Controller) Get(key interface{}) interface{} { 114 | c.instance.StorageMu.RLock() 115 | defer c.instance.StorageMu.RUnlock() 116 | return c.instance.Storage[key] 117 | } 118 | 119 | // Set safely sets a value on the Instance's storage. 120 | func (c *Controller) Set(key, val interface{}) { 121 | c.instance.StorageMu.Lock() 122 | c.instance.Storage[key] = val 123 | c.instance.StorageMu.Unlock() 124 | } 125 | 126 | // NewTestController creates a new Controller for 127 | // the server type and input specified. The filename 128 | // is "Testfile". If the server type is not empty and 129 | // is plugged in, a context will be created so that 130 | // the results of setup functions can be checked for 131 | // correctness. 132 | // 133 | // Used only for testing, but exported so plugins can 134 | // use this for convenience. 135 | func NewTestController(serverType, input string) *Controller { 136 | testInst := &Instance{serverType: serverType, Storage: make(map[interface{}]interface{})} 137 | if stype, err := getServerType(serverType); err == nil { 138 | testInst.context = stype.NewContext(testInst) 139 | } 140 | return &Controller{ 141 | instance: testInst, 142 | Dispenser: caddyfile.NewDispenser("Testfile", strings.NewReader(input)), 143 | OncePerServerBlock: func(f func() error) error { return f() }, 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /caddy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "reflect" 21 | "sync" 22 | "testing" 23 | 24 | "github.com/coredns/caddy/caddyfile" 25 | ) 26 | 27 | /* 28 | // TODO 29 | func TestCaddyStartStop(t *testing.T) { 30 | caddyfile := "localhost:1984" 31 | 32 | for i := 0; i < 2; i++ { 33 | _, err := Start(CaddyfileInput{Contents: []byte(caddyfile)}) 34 | if err != nil { 35 | t.Fatalf("Error starting, iteration %d: %v", i, err) 36 | } 37 | 38 | client := http.Client{ 39 | Timeout: time.Duration(2 * time.Second), 40 | } 41 | resp, err := client.Get("http://localhost:1984") 42 | if err != nil { 43 | t.Fatalf("Expected GET request to succeed (iteration %d), but it failed: %v", i, err) 44 | } 45 | resp.Body.Close() 46 | 47 | err = Stop() 48 | if err != nil { 49 | t.Fatalf("Error stopping, iteration %d: %v", i, err) 50 | } 51 | } 52 | } 53 | */ 54 | 55 | // CallbackTestContext implements Context interface 56 | type CallbackTestContext struct { 57 | // If MakeServersFail is set to true then MakeServers returns an error 58 | MakeServersFail bool 59 | } 60 | 61 | func (h *CallbackTestContext) InspectServerBlocks(name string, sblock []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) { 62 | return sblock, nil 63 | } 64 | func (h *CallbackTestContext) MakeServers() ([]Server, error) { 65 | if h.MakeServersFail { 66 | return make([]Server, 0), fmt.Errorf("MakeServers failed") 67 | } 68 | return make([]Server, 0), nil 69 | } 70 | 71 | func TestCaddyRestartCallbacks(t *testing.T) { 72 | for i, test := range []struct { 73 | restartFail bool 74 | expectedCalls []string 75 | }{ 76 | {false, []string{"OnRestart", "OnShutdown"}}, 77 | {true, []string{"OnRestart", "OnRestartFailed"}}, 78 | } { 79 | serverName := fmt.Sprintf("%v", i) 80 | // RegisterServerType to make successful restart possible 81 | RegisterServerType(serverName, ServerType{ 82 | Directives: func() []string { return []string{} }, 83 | // If MakeServersFail is true then the restart will fail due to context failure 84 | NewContext: func(inst *Instance) Context { return &CallbackTestContext{MakeServersFail: test.restartFail} }, 85 | }) 86 | c := NewTestController(serverName, "") 87 | c.instance = &Instance{ 88 | serverType: serverName, 89 | wg: new(sync.WaitGroup), 90 | } 91 | 92 | // Register callbacks which save the calls order 93 | calls := make([]string, 0) 94 | c.OnRestart(func() error { 95 | calls = append(calls, "OnRestart") 96 | return nil 97 | }) 98 | c.OnRestartFailed(func() error { 99 | calls = append(calls, "OnRestartFailed") 100 | return nil 101 | }) 102 | c.OnShutdown(func() error { 103 | calls = append(calls, "OnShutdown") 104 | return nil 105 | }) 106 | 107 | _, err := c.instance.Restart(CaddyfileInput{Contents: []byte(""), ServerTypeName: serverName}) 108 | if err != nil { 109 | log.Printf("[ERROR] Restart failed: %v", err) 110 | } 111 | 112 | if !reflect.DeepEqual(calls, test.expectedCalls) { 113 | t.Errorf("Test %d: Callbacks expected: %v, got: %v", i, test.expectedCalls, calls) 114 | } 115 | 116 | err = c.instance.Stop() 117 | if err != nil { 118 | log.Printf("[ERROR] Stop failed: %v", err) 119 | } 120 | 121 | c.instance.Wait() 122 | } 123 | 124 | } 125 | 126 | func TestIsLoopback(t *testing.T) { 127 | for i, test := range []struct { 128 | input string 129 | expect bool 130 | }{ 131 | {"example.com", false}, 132 | {"localhost", true}, 133 | {"localhost:1234", true}, 134 | {"localhost:", true}, 135 | {"127.0.0.1", true}, 136 | {"127.0.0.1:443", true}, 137 | {"127.0.1.5", true}, 138 | {"10.0.0.5", false}, 139 | {"12.7.0.1", false}, 140 | {"[::1]", true}, 141 | {"[::1]:1234", true}, 142 | {"::1", true}, 143 | {"::", false}, 144 | {"[::]", false}, 145 | {"local", false}, 146 | } { 147 | if got, want := IsLoopback(test.input), test.expect; got != want { 148 | t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got) 149 | } 150 | } 151 | } 152 | 153 | func TestIsInternal(t *testing.T) { 154 | for i, test := range []struct { 155 | input string 156 | expect bool 157 | }{ 158 | {"9.255.255.255", false}, 159 | {"10.0.0.0", true}, 160 | {"10.0.0.1", true}, 161 | {"10.255.255.254", true}, 162 | {"10.255.255.255", true}, 163 | {"11.0.0.0", false}, 164 | {"10.0.0.5:1234", true}, 165 | {"11.0.0.5:1234", false}, 166 | 167 | {"172.15.255.255", false}, 168 | {"172.16.0.0", true}, 169 | {"172.16.0.1", true}, 170 | {"172.31.255.254", true}, 171 | {"172.31.255.255", true}, 172 | {"172.32.0.0", false}, 173 | {"172.16.0.1:1234", true}, 174 | 175 | {"192.167.255.255", false}, 176 | {"192.168.0.0", true}, 177 | {"192.168.0.1", true}, 178 | {"192.168.255.254", true}, 179 | {"192.168.255.255", true}, 180 | {"192.169.0.0", false}, 181 | {"192.168.0.1:1234", true}, 182 | 183 | {"fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", false}, 184 | {"fc00::", true}, 185 | {"fc00::1", true}, 186 | {"[fc00::1]", true}, 187 | {"[fc00::1]:8888", true}, 188 | {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", true}, 189 | {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true}, 190 | {"fe00::", false}, 191 | {"fd12:3456:789a:1::1:1234", true}, 192 | 193 | {"example.com", false}, 194 | {"localhost", false}, 195 | {"localhost:1234", false}, 196 | {"localhost:", false}, 197 | {"127.0.0.1", false}, 198 | {"127.0.0.1:443", false}, 199 | {"127.0.1.5", false}, 200 | {"12.7.0.1", false}, 201 | {"[::1]", false}, 202 | {"[::1]:1234", false}, 203 | {"::1", false}, 204 | {"::", false}, 205 | {"[::]", false}, 206 | {"local", false}, 207 | } { 208 | if got, want := IsInternal(test.input), test.expect; got != want { 209 | t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got) 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /upgrade.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "encoding/gob" 19 | "fmt" 20 | "io/ioutil" 21 | "log" 22 | "os" 23 | "os/exec" 24 | "sync" 25 | ) 26 | 27 | func init() { 28 | // register CaddyfileInput with gob so it knows into 29 | // which concrete type to decode an Input interface 30 | gob.Register(CaddyfileInput{}) 31 | } 32 | 33 | // IsUpgrade returns true if this process is part of an upgrade 34 | // where a parent caddy process spawned this one to upgrade 35 | // the binary. 36 | func IsUpgrade() bool { 37 | mu.Lock() 38 | defer mu.Unlock() 39 | return isUpgrade 40 | } 41 | 42 | // Upgrade re-launches the process, preserving the listeners 43 | // for a graceful upgrade. It does NOT load new configuration; 44 | // it only starts the process anew with the current config. 45 | // This makes it possible to perform zero-downtime binary upgrades. 46 | // 47 | // TODO: For more information when debugging, see: 48 | // https://forum.golangbridge.org/t/bind-address-already-in-use-even-after-listener-closed/1510?u=matt 49 | // https://github.com/mholt/shared-conn 50 | func Upgrade() error { 51 | log.Println("[INFO] Upgrading") 52 | 53 | // use existing Caddyfile; do not change configuration during upgrade 54 | currentCaddyfile, _, err := getCurrentCaddyfile() 55 | if err != nil { 56 | return err 57 | } 58 | 59 | if len(os.Args) == 0 { // this should never happen, but... 60 | os.Args = []string{""} 61 | } 62 | 63 | // tell the child that it's a restart 64 | env := os.Environ() 65 | if !IsUpgrade() { 66 | env = append(env, "CADDY__UPGRADE=1") 67 | } 68 | 69 | // prepare our payload to the child process 70 | cdyfileGob := transferGob{ 71 | ListenerFds: make(map[string]uintptr), 72 | Caddyfile: currentCaddyfile, 73 | } 74 | 75 | // prepare a pipe to the fork's stdin so it can get the Caddyfile 76 | rpipe, wpipe, err := os.Pipe() 77 | if err != nil { 78 | return err 79 | } 80 | 81 | // prepare a pipe that the child process will use to communicate 82 | // its success with us by sending > 0 bytes 83 | sigrpipe, sigwpipe, err := os.Pipe() 84 | if err != nil { 85 | return err 86 | } 87 | 88 | // pass along relevant file descriptors to child process; ordering 89 | // is very important since we rely on these being in certain positions. 90 | extraFiles := []*os.File{sigwpipe} // fd 3 91 | 92 | // add file descriptors of all the sockets 93 | for i, j := 0, 0; ; i++ { 94 | instancesMu.Lock() 95 | if i >= len(instances) { 96 | instancesMu.Unlock() 97 | break 98 | } 99 | inst := instances[i] 100 | instancesMu.Unlock() 101 | 102 | for _, s := range inst.servers { 103 | gs, gracefulOk := s.server.(GracefulServer) 104 | ln, lnOk := s.listener.(Listener) 105 | pc, pcOk := s.packet.(PacketConn) 106 | if gracefulOk { 107 | if lnOk { 108 | lnFile, _ := ln.File() 109 | extraFiles = append(extraFiles, lnFile) 110 | cdyfileGob.ListenerFds["tcp"+gs.Address()] = uintptr(4 + j) // 4 fds come before any of the listeners 111 | j++ 112 | } 113 | if pcOk { 114 | pcFile, _ := pc.File() 115 | extraFiles = append(extraFiles, pcFile) 116 | cdyfileGob.ListenerFds["udp"+gs.Address()] = uintptr(4 + j) // 4 fds come before any of the listeners 117 | j++ 118 | } 119 | } 120 | } 121 | } 122 | 123 | // set up the command 124 | cmd := exec.Command(os.Args[0], os.Args[1:]...) 125 | cmd.Stdin = rpipe // fd 0 126 | cmd.Stdout = os.Stdout // fd 1 127 | cmd.Stderr = os.Stderr // fd 2 128 | cmd.ExtraFiles = extraFiles 129 | cmd.Env = env 130 | 131 | // spawn the child process 132 | err = cmd.Start() 133 | if err != nil { 134 | return err 135 | } 136 | 137 | // immediately close our dup'ed fds and the write end of our signal pipe 138 | for _, f := range extraFiles { 139 | err = f.Close() 140 | if err != nil { 141 | return err 142 | } 143 | } 144 | 145 | // feed Caddyfile to the child 146 | err = gob.NewEncoder(wpipe).Encode(cdyfileGob) 147 | if err != nil { 148 | return err 149 | } 150 | err = wpipe.Close() 151 | if err != nil { 152 | return err 153 | } 154 | 155 | // determine whether child startup succeeded 156 | answer, readErr := ioutil.ReadAll(sigrpipe) 157 | if len(answer) == 0 { 158 | cmdErr := cmd.Wait() // get exit status 159 | errStr := fmt.Sprintf("child failed to initialize: %v", cmdErr) 160 | if readErr != nil { 161 | errStr += fmt.Sprintf(" - additionally, error communicating with child process: %v", readErr) 162 | } 163 | return fmt.Errorf(errStr) 164 | } 165 | 166 | // looks like child is successful; we can exit gracefully. 167 | log.Println("[INFO] Upgrade finished") 168 | return Stop() 169 | } 170 | 171 | // getCurrentCaddyfile gets the Caddyfile used by the 172 | // current (first) Instance and returns both of them. 173 | func getCurrentCaddyfile() (Input, *Instance, error) { 174 | instancesMu.Lock() 175 | if len(instances) == 0 { 176 | instancesMu.Unlock() 177 | return nil, nil, fmt.Errorf("no server instances are fully running") 178 | } 179 | inst := instances[0] 180 | instancesMu.Unlock() 181 | 182 | currentCaddyfile := inst.caddyfileInput 183 | if currentCaddyfile == nil { 184 | // hmm, did spawning process forget to close stdin? Anyhow, this is unusual. 185 | return nil, inst, fmt.Errorf("no Caddyfile to reload (was stdin left open?)") 186 | } 187 | return currentCaddyfile, inst, nil 188 | } 189 | 190 | // signalSuccessToParent tells the parent our status using pipe at index 3. 191 | // If this process is not a restart, this function does nothing. 192 | // Calling this function once this process has successfully initialized 193 | // is vital so that the parent process can unblock and kill itself. 194 | // This function is idempotent; it executes at most once per process. 195 | func signalSuccessToParent() { 196 | signalParentOnce.Do(func() { 197 | if IsUpgrade() { 198 | ppipe := os.NewFile(3, "") // parent is reading from pipe at index 3 199 | _, err := ppipe.Write([]byte("success")) // we must send some bytes to the parent 200 | if err != nil { 201 | log.Printf("[ERROR] Communicating successful init to parent: %v", err) 202 | } 203 | ppipe.Close() 204 | } 205 | }) 206 | } 207 | 208 | // signalParentOnce is used to make sure that the parent is only 209 | // signaled once; doing so more than once breaks whatever socket is 210 | // at fd 4 (TODO: the reason for this is still unclear - to reproduce, 211 | // call Stop() and Start() in succession at least once after a 212 | // restart, then try loading first host of Caddyfile in the browser 213 | // - this was pre-v0.9; this code and godoc is borrowed from the 214 | // implementation then, but I'm not sure if it's been fixed yet, as 215 | // of v0.10.7). Do not use this directly; call signalSuccessToParent 216 | // instead. 217 | var signalParentOnce sync.Once 218 | 219 | // transferGob is used if this is a child process as part of 220 | // a graceful upgrade; it is used to map listeners to their 221 | // index in the list of inherited file descriptors. This 222 | // variable is not safe for concurrent access. 223 | var loadedGob transferGob 224 | 225 | // transferGob maps bind address to index of the file descriptor 226 | // in the Files array passed to the child process. It also contains 227 | // the Caddyfile contents and any other state needed by the new process. 228 | // Used only during graceful upgrades. 229 | type transferGob struct { 230 | ListenerFds map[string]uintptr 231 | Caddyfile Input 232 | } 233 | -------------------------------------------------------------------------------- /caddyfile/dispenser.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Dispenser is a type that dispenses tokens, similarly to a lexer, 25 | // except that it can do so with some notion of structure and has 26 | // some really convenient methods. 27 | type Dispenser struct { 28 | filename string 29 | tokens []Token 30 | cursor int 31 | nesting int 32 | } 33 | 34 | // NewDispenser returns a Dispenser, ready to use for parsing the given input. 35 | func NewDispenser(filename string, input io.Reader) Dispenser { 36 | tokens, _ := allTokens(input) // ignoring error because nothing to do with it 37 | return Dispenser{ 38 | filename: filename, 39 | tokens: tokens, 40 | cursor: -1, 41 | } 42 | } 43 | 44 | // NewDispenserTokens returns a Dispenser filled with the given tokens. 45 | func NewDispenserTokens(filename string, tokens []Token) Dispenser { 46 | return Dispenser{ 47 | filename: filename, 48 | tokens: tokens, 49 | cursor: -1, 50 | } 51 | } 52 | 53 | // Next loads the next token. Returns true if a token 54 | // was loaded; false otherwise. If false, all tokens 55 | // have been consumed. 56 | func (d *Dispenser) Next() bool { 57 | if d.cursor < len(d.tokens)-1 { 58 | d.cursor++ 59 | return true 60 | } 61 | return false 62 | } 63 | 64 | // NextArg loads the next token if it is on the same 65 | // line. Returns true if a token was loaded; false 66 | // otherwise. If false, all tokens on the line have 67 | // been consumed. It handles imported tokens correctly. 68 | func (d *Dispenser) NextArg() bool { 69 | if d.cursor < 0 { 70 | d.cursor++ 71 | return true 72 | } 73 | if d.cursor >= len(d.tokens) { 74 | return false 75 | } 76 | if d.cursor < len(d.tokens)-1 && 77 | d.tokens[d.cursor].File == d.tokens[d.cursor+1].File && 78 | d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line { 79 | d.cursor++ 80 | return true 81 | } 82 | return false 83 | } 84 | 85 | // NextLine loads the next token only if it is not on the same 86 | // line as the current token, and returns true if a token was 87 | // loaded; false otherwise. If false, there is not another token 88 | // or it is on the same line. It handles imported tokens correctly. 89 | func (d *Dispenser) NextLine() bool { 90 | if d.cursor < 0 { 91 | d.cursor++ 92 | return true 93 | } 94 | if d.cursor >= len(d.tokens) { 95 | return false 96 | } 97 | if d.cursor < len(d.tokens)-1 && 98 | (d.tokens[d.cursor].File != d.tokens[d.cursor+1].File || 99 | d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) { 100 | d.cursor++ 101 | return true 102 | } 103 | return false 104 | } 105 | 106 | // NextBlock can be used as the condition of a for loop 107 | // to load the next token as long as it opens a block or 108 | // is already in a block. It returns true if a token was 109 | // loaded, or false when the block's closing curly brace 110 | // was loaded and thus the block ended. Nested blocks are 111 | // not supported. 112 | func (d *Dispenser) NextBlock() bool { 113 | if d.nesting > 0 { 114 | if !d.Next() { 115 | return false 116 | } 117 | if d.Val() == "}" { 118 | d.nesting-- 119 | return false 120 | } 121 | return true 122 | } 123 | if !d.NextArg() { // block must open on same line 124 | return false 125 | } 126 | if d.Val() != "{" { 127 | d.cursor-- // roll back if not opening brace 128 | return false 129 | } 130 | if !d.Next() { 131 | return false 132 | } 133 | if d.Val() == "}" { 134 | // Open and then closed right away 135 | return false 136 | } 137 | d.nesting++ 138 | return true 139 | } 140 | 141 | // Val gets the text of the current token. If there is no token 142 | // loaded, it returns empty string. 143 | func (d *Dispenser) Val() string { 144 | if d.cursor < 0 || d.cursor >= len(d.tokens) { 145 | return "" 146 | } 147 | return d.tokens[d.cursor].Text 148 | } 149 | 150 | // Line gets the line number of the current token. If there is no token 151 | // loaded, it returns 0. 152 | func (d *Dispenser) Line() int { 153 | if d.cursor < 0 || d.cursor >= len(d.tokens) { 154 | return 0 155 | } 156 | return d.tokens[d.cursor].Line 157 | } 158 | 159 | // File gets the filename of the current token. If there is no token loaded, 160 | // it returns the filename originally given when parsing started. 161 | func (d *Dispenser) File() string { 162 | if d.cursor < 0 || d.cursor >= len(d.tokens) { 163 | return d.filename 164 | } 165 | if tokenFilename := d.tokens[d.cursor].File; tokenFilename != "" { 166 | return tokenFilename 167 | } 168 | return d.filename 169 | } 170 | 171 | // Args is a convenience function that loads the next arguments 172 | // (tokens on the same line) into an arbitrary number of strings 173 | // pointed to in targets. If there are fewer tokens available 174 | // than string pointers, the remaining strings will not be changed 175 | // and false will be returned. If there were enough tokens available 176 | // to fill the arguments, then true will be returned. 177 | func (d *Dispenser) Args(targets ...*string) bool { 178 | enough := true 179 | for i := 0; i < len(targets); i++ { 180 | if !d.NextArg() { 181 | enough = false 182 | break 183 | } 184 | *targets[i] = d.Val() 185 | } 186 | return enough 187 | } 188 | 189 | // RemainingArgs loads any more arguments (tokens on the same line) 190 | // into a slice and returns them. Open curly brace tokens also indicate 191 | // the end of arguments, and the curly brace is not included in 192 | // the return value nor is it loaded. 193 | func (d *Dispenser) RemainingArgs() []string { 194 | var args []string 195 | 196 | for d.NextArg() { 197 | if d.Val() == "{" { 198 | d.cursor-- 199 | break 200 | } 201 | args = append(args, d.Val()) 202 | } 203 | 204 | return args 205 | } 206 | 207 | // ArgErr returns an argument error, meaning that another 208 | // argument was expected but not found. In other words, 209 | // a line break or open curly brace was encountered instead of 210 | // an argument. 211 | func (d *Dispenser) ArgErr() error { 212 | if d.Val() == "{" { 213 | return d.Err("Unexpected token '{', expecting argument") 214 | } 215 | return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val()) 216 | } 217 | 218 | // SyntaxErr creates a generic syntax error which explains what was 219 | // found and what was expected. 220 | func (d *Dispenser) SyntaxErr(expected string) error { 221 | msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected) 222 | return errors.New(msg) 223 | } 224 | 225 | // EOFErr returns an error indicating that the dispenser reached 226 | // the end of the input when searching for the next token. 227 | func (d *Dispenser) EOFErr() error { 228 | return d.Errf("Unexpected EOF") 229 | } 230 | 231 | // Err generates a custom parse-time error with a message of msg. 232 | func (d *Dispenser) Err(msg string) error { 233 | msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg) 234 | return errors.New(msg) 235 | } 236 | 237 | // Errf is like Err, but for formatted error messages 238 | func (d *Dispenser) Errf(format string, args ...interface{}) error { 239 | return d.Err(fmt.Sprintf(format, args...)) 240 | } 241 | 242 | // numLineBreaks counts how many line breaks are in the token 243 | // value given by the token index tknIdx. It returns 0 if the 244 | // token does not exist or there are no line breaks. 245 | func (d *Dispenser) numLineBreaks(tknIdx int) int { 246 | if tknIdx < 0 || tknIdx >= len(d.tokens) { 247 | return 0 248 | } 249 | return strings.Count(d.tokens[tknIdx].Text, "\n") 250 | } 251 | 252 | // isNewLine determines whether the current token is on a different 253 | // line (higher line number) than the previous token. It handles imported 254 | // tokens correctly. If there isn't a previous token, it returns true. 255 | func (d *Dispenser) isNewLine() bool { 256 | if d.cursor < 1 { 257 | return true 258 | } 259 | if d.cursor > len(d.tokens)-1 { 260 | return false 261 | } 262 | return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File || 263 | d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line 264 | } 265 | -------------------------------------------------------------------------------- /commands_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | "strings" 21 | "testing" 22 | ) 23 | 24 | func TestParseUnixCommand(t *testing.T) { 25 | tests := []struct { 26 | input string 27 | expected []string 28 | }{ 29 | // 0 - empty command 30 | { 31 | input: ``, 32 | expected: []string{}, 33 | }, 34 | // 1 - command without arguments 35 | { 36 | input: `command`, 37 | expected: []string{`command`}, 38 | }, 39 | // 2 - command with single argument 40 | { 41 | input: `command arg1`, 42 | expected: []string{`command`, `arg1`}, 43 | }, 44 | // 3 - command with multiple arguments 45 | { 46 | input: `command arg1 arg2`, 47 | expected: []string{`command`, `arg1`, `arg2`}, 48 | }, 49 | // 4 - command with single argument with space character - in quotes 50 | { 51 | input: `command "arg1 arg1"`, 52 | expected: []string{`command`, `arg1 arg1`}, 53 | }, 54 | // 5 - command with multiple spaces and tab character 55 | { 56 | input: "command arg1 arg2\targ3", 57 | expected: []string{`command`, `arg1`, `arg2`, `arg3`}, 58 | }, 59 | // 6 - command with single argument with space character - escaped with backspace 60 | { 61 | input: `command arg1\ arg2`, 62 | expected: []string{`command`, `arg1 arg2`}, 63 | }, 64 | // 7 - single quotes should escape special chars 65 | { 66 | input: `command 'arg1\ arg2'`, 67 | expected: []string{`command`, `arg1\ arg2`}, 68 | }, 69 | } 70 | 71 | for i, test := range tests { 72 | errorPrefix := fmt.Sprintf("Test [%d]: ", i) 73 | errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) 74 | actual, _ := parseUnixCommand(test.input) 75 | if len(actual) != len(test.expected) { 76 | t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual) 77 | continue 78 | } 79 | for j := 0; j < len(actual); j++ { 80 | if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart { 81 | t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j) 82 | } 83 | } 84 | } 85 | } 86 | 87 | func TestParseWindowsCommand(t *testing.T) { 88 | tests := []struct { 89 | input string 90 | expected []string 91 | }{ 92 | { // 0 - empty command - do not fail 93 | input: ``, 94 | expected: []string{}, 95 | }, 96 | { // 1 - cmd without args 97 | input: `cmd`, 98 | expected: []string{`cmd`}, 99 | }, 100 | { // 2 - multiple args 101 | input: `cmd arg1 arg2`, 102 | expected: []string{`cmd`, `arg1`, `arg2`}, 103 | }, 104 | { // 3 - multiple args with space 105 | input: `cmd "combined arg" arg2`, 106 | expected: []string{`cmd`, `combined arg`, `arg2`}, 107 | }, 108 | { // 4 - path without spaces 109 | input: `mkdir C:\Windows\foo\bar`, 110 | expected: []string{`mkdir`, `C:\Windows\foo\bar`}, 111 | }, 112 | { // 5 - command with space in quotes 113 | input: `"command here"`, 114 | expected: []string{`command here`}, 115 | }, 116 | { // 6 - argument with escaped quotes (two quotes) 117 | input: `cmd ""arg""`, 118 | expected: []string{`cmd`, `"arg"`}, 119 | }, 120 | { // 7 - argument with escaped quotes (backslash) 121 | input: `cmd \"arg\"`, 122 | expected: []string{`cmd`, `"arg"`}, 123 | }, 124 | { // 8 - two quotes (escaped) inside an inQuote element 125 | input: `cmd "a ""quoted value"`, 126 | expected: []string{`cmd`, `a "quoted value`}, 127 | }, 128 | // TODO - see how many quotes are displayed if we use "", """, """"""" 129 | { // 9 - two quotes outside an inQuote element 130 | input: `cmd a ""quoted value`, 131 | expected: []string{`cmd`, `a`, `"quoted`, `value`}, 132 | }, 133 | { // 10 - path with space in quotes 134 | input: `mkdir "C:\directory name\foobar"`, 135 | expected: []string{`mkdir`, `C:\directory name\foobar`}, 136 | }, 137 | { // 11 - space without quotes 138 | input: `mkdir C:\ space`, 139 | expected: []string{`mkdir`, `C:\`, `space`}, 140 | }, 141 | { // 12 - space in quotes 142 | input: `mkdir "C:\ space"`, 143 | expected: []string{`mkdir`, `C:\ space`}, 144 | }, 145 | { // 13 - UNC 146 | input: `mkdir \\?\C:\Users`, 147 | expected: []string{`mkdir`, `\\?\C:\Users`}, 148 | }, 149 | { // 14 - UNC with space 150 | input: `mkdir "\\?\C:\Program Files"`, 151 | expected: []string{`mkdir`, `\\?\C:\Program Files`}, 152 | }, 153 | 154 | { // 15 - unclosed quotes - treat as if the path ends with quote 155 | input: `mkdir "c:\Program files`, 156 | expected: []string{`mkdir`, `c:\Program files`}, 157 | }, 158 | { // 16 - quotes used inside the argument 159 | input: `mkdir "c:\P"rogra"m f"iles`, 160 | expected: []string{`mkdir`, `c:\Program files`}, 161 | }, 162 | } 163 | 164 | for i, test := range tests { 165 | errorPrefix := fmt.Sprintf("Test [%d]: ", i) 166 | errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) 167 | 168 | actual := parseWindowsCommand(test.input) 169 | if len(actual) != len(test.expected) { 170 | t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual) 171 | continue 172 | } 173 | for j := 0; j < len(actual); j++ { 174 | if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart { 175 | t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j) 176 | } 177 | } 178 | } 179 | } 180 | 181 | func TestSplitCommandAndArgs(t *testing.T) { 182 | 183 | // force linux parsing. It's more robust and covers error cases 184 | runtimeGoos = "linux" 185 | defer func() { 186 | runtimeGoos = runtime.GOOS 187 | }() 188 | 189 | var parseErrorContent = "error parsing command:" 190 | var noCommandErrContent = "no command contained in" 191 | 192 | tests := []struct { 193 | input string 194 | expectedCommand string 195 | expectedArgs []string 196 | expectedErrContent string 197 | }{ 198 | // 0 - empty command 199 | { 200 | input: ``, 201 | expectedCommand: ``, 202 | expectedArgs: nil, 203 | expectedErrContent: noCommandErrContent, 204 | }, 205 | // 1 - command without arguments 206 | { 207 | input: `command`, 208 | expectedCommand: `command`, 209 | expectedArgs: nil, 210 | expectedErrContent: ``, 211 | }, 212 | // 2 - command with single argument 213 | { 214 | input: `command arg1`, 215 | expectedCommand: `command`, 216 | expectedArgs: []string{`arg1`}, 217 | expectedErrContent: ``, 218 | }, 219 | // 3 - command with multiple arguments 220 | { 221 | input: `command arg1 arg2`, 222 | expectedCommand: `command`, 223 | expectedArgs: []string{`arg1`, `arg2`}, 224 | expectedErrContent: ``, 225 | }, 226 | // 4 - command with unclosed quotes 227 | { 228 | input: `command "arg1 arg2`, 229 | expectedCommand: "", 230 | expectedArgs: nil, 231 | expectedErrContent: parseErrorContent, 232 | }, 233 | // 5 - command with unclosed quotes 234 | { 235 | input: `command 'arg1 arg2"`, 236 | expectedCommand: "", 237 | expectedArgs: nil, 238 | expectedErrContent: parseErrorContent, 239 | }, 240 | } 241 | 242 | for i, test := range tests { 243 | errorPrefix := fmt.Sprintf("Test [%d]: ", i) 244 | errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input) 245 | actualCommand, actualArgs, actualErr := SplitCommandAndArgs(test.input) 246 | 247 | // test if error matches expectation 248 | if test.expectedErrContent != "" { 249 | if actualErr == nil { 250 | t.Errorf(errorPrefix+"Expected error with content [%s], found no error."+errorSuffix, test.expectedErrContent) 251 | } else if !strings.Contains(actualErr.Error(), test.expectedErrContent) { 252 | t.Errorf(errorPrefix+"Expected error with content [%s], found [%v]."+errorSuffix, test.expectedErrContent, actualErr) 253 | } 254 | } else if actualErr != nil { 255 | t.Errorf(errorPrefix+"Expected no error, found [%v]."+errorSuffix, actualErr) 256 | } 257 | 258 | // test if command matches 259 | if test.expectedCommand != actualCommand { 260 | t.Errorf(errorPrefix+"Expected command: [%s], actual: [%s]."+errorSuffix, test.expectedCommand, actualCommand) 261 | } 262 | 263 | // test if arguments match 264 | if len(test.expectedArgs) != len(actualArgs) { 265 | t.Errorf(errorPrefix+"Wrong number of arguments! Expected [%v], actual [%v]."+errorSuffix, test.expectedArgs, actualArgs) 266 | } else { 267 | // test args only if the count matches. 268 | for j, actualArg := range actualArgs { 269 | expectedArg := test.expectedArgs[j] 270 | if actualArg != expectedArg { 271 | t.Errorf(errorPrefix+"Argument at position [%d] differ! Expected [%s], actual [%s]"+errorSuffix, j, expectedArg, actualArg) 272 | } 273 | } 274 | } 275 | } 276 | } 277 | 278 | func ExampleSplitCommandAndArgs() { 279 | var commandLine string 280 | var command string 281 | var args []string 282 | 283 | // just for the test - change GOOS and reset it at the end of the test 284 | runtimeGoos = "windows" 285 | defer func() { 286 | runtimeGoos = runtime.GOOS 287 | }() 288 | 289 | commandLine = `mkdir /P "C:\Program Files"` 290 | command, args, _ = SplitCommandAndArgs(commandLine) 291 | 292 | fmt.Printf("Windows: %s: %s [%s]\n", commandLine, command, strings.Join(args, ",")) 293 | 294 | // set GOOS to linux 295 | runtimeGoos = "linux" 296 | 297 | commandLine = `mkdir -p /path/with\ space` 298 | command, args, _ = SplitCommandAndArgs(commandLine) 299 | 300 | fmt.Printf("Linux: %s: %s [%s]\n", commandLine, command, strings.Join(args, ",")) 301 | 302 | // Output: 303 | // Windows: mkdir /P "C:\Program Files": mkdir [/P,C:\Program Files] 304 | // Linux: mkdir -p /path/with\ space: mkdir [-p,/path/with space] 305 | } 306 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | THIS IS A FORK OF CADDY v1 - EVERYTHING IS STRIPPED EXCEPT THE PIECES NEEDED IN COREDNS. 2 | 3 | Issues are not enabled in this repository. Please raise any issues in coredns/coredns. 4 | 5 | --- 6 | 7 | 8 | Caddy is a **production-ready** open-source web server that is fast, easy to use, and makes you more productive. 9 | 10 | Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/caddyserver/caddy/wiki/Running-Caddy-on-Android). 11 | 12 |

13 | Thanks to our special sponsor: 14 |

15 | Relica - Cross-platform file backup to the cloud, local disks, or other computers 16 |

17 | 18 | ## Menu 19 | 20 | - [Features](#features) 21 | - [Install](#install) 22 | - [Quick Start](#quick-start) 23 | - [Running in Production](#running-in-production) 24 | - [Contributing](#contributing) 25 | - [Donors](#donors) 26 | - [About the Project](#about-the-project) 27 | 28 | ## Features 29 | 30 | - **Easy configuration** with the Caddyfile 31 | - **Automatic HTTPS** on by default (via [Let's Encrypt](https://letsencrypt.org)) 32 | - **HTTP/2** by default 33 | - **Virtual hosting** so multiple sites just work 34 | - Experimental **QUIC support** for cutting-edge transmissions 35 | - TLS session ticket **key rotation** for more secure connections 36 | - **Extensible with plugins** because a convenient web server is a helpful one 37 | - **Runs anywhere** with **no external dependencies** (not even libc) 38 | 39 | [See a more complete list of features built into Caddy.](https://caddyserver.com/#features) On top of all those, Caddy does even more with plugins: choose which plugins you want at [download](https://caddyserver.com/download). 40 | 41 | Altogether, Caddy can do things other web servers simply cannot do. Its features and plugins save you time and mistakes, and will cheer you up. Your Caddy instance takes care of the details for you! 42 | 43 | 44 |

45 | Powered by 46 |
47 | CertMagic 48 |

49 | 50 | 51 | ## Install 52 | 53 | Caddy binaries have no dependencies and are available for every platform. Get Caddy any of these ways: 54 | 55 | - **[Download page](https://caddyserver.com/download)** (RECOMMENDED) allows you to customize your build in the browser 56 | - **[Latest release](https://github.com/caddyserver/caddy/releases/latest)** for pre-built, vanilla binaries 57 | - **[AWS Marketplace](https://aws.amazon.com/marketplace/pp/B07J1WNK75?qid=1539015041932&sr=0-1&ref_=srh_res_product_title&cl_spe=C)** makes it easy to deploy directly to your cloud environment. 58 | Get Caddy on the AWS Marketplace 59 | 60 | 61 | ## Build 62 | 63 | To build from source you need **[Git](https://git-scm.com/downloads)** and **[Go](https://golang.org/doc/install)** (1.13 or newer). 64 | 65 | **To build Caddy without plugins:** 66 | 67 | - Run `go get github.com/caddyserver/caddy/caddy` 68 | 69 | Caddy will be installed to your `$GOPATH/bin` folder. 70 | 71 | With these instructions, the binary will not have embedded version information (see [golang/go#29228](https://github.com/golang/go/issues/29228)), but it is fine for a quick start. 72 | 73 | **To build Caddy with plugins (and with version information):** 74 | 75 | There is no need to modify the Caddy code to build it with plugins. We will create a simple Go module with our own `main()` that you can use to make custom Caddy builds. 76 | - Create a new folder anywhere and within create a Go file (with an extension of `.go`, such as `main.go`) with the contents below, adjusting to import the plugins you want to include: 77 | ```go 78 | package main 79 | 80 | import ( 81 | "github.com/caddyserver/caddy/caddy/caddymain" 82 | 83 | // plug in plugins here, for example: 84 | // _ "import/path/here" 85 | ) 86 | 87 | func main() { 88 | // optional: disable telemetry 89 | // caddymain.EnableTelemetry = false 90 | caddymain.Run() 91 | } 92 | ``` 93 | 3. `go mod init caddy` 94 | 4. Run `go get github.com/caddyserver/caddy` 95 | 5. `go install` will then create your binary at `$GOPATH/bin`, or `go build` will put it in the current directory. 96 | 97 | **To install Caddy's source code for development:** 98 | 99 | - Run `git clone https://github.com/caddyserver/caddy.git` in any folder (doesn't have to be in GOPATH). 100 | 101 | You can make changes to the source code from that clone and checkout any commit or tag you wish to develop on. 102 | 103 | When building from source, telemetry is enabled by default. You can disable it by changing `caddymain.EnableTelemetry = false` in run.go, or use the `-disabled-metrics` flag at runtime to disable only certain metrics. 104 | 105 | 106 | ## Quick Start 107 | 108 | To serve static files from the current working directory, run: 109 | 110 | ``` 111 | caddy 112 | ``` 113 | 114 | Caddy's default port is 2015, so open your browser to [http://localhost:2015](http://localhost:2015). 115 | 116 | ### Go from 0 to HTTPS in 5 seconds 117 | 118 | If the `caddy` binary has permission to bind to low ports and your domain name's DNS records point to the machine you're on: 119 | 120 | ``` 121 | caddy -host example.com 122 | ``` 123 | 124 | This command serves static files from the current directory over HTTPS. Certificates are automatically obtained and renewed for you! Caddy is also automatically configuring ports 80 and 443 for you, and redirecting HTTP to HTTPS. Cool, huh? 125 | 126 | ### Customizing your site 127 | 128 | To customize how your site is served, create a file named Caddyfile by your site and paste this into it: 129 | 130 | ```plain 131 | localhost 132 | 133 | push 134 | browse 135 | websocket /echo cat 136 | ext .html 137 | log /var/log/access.log 138 | proxy /api 127.0.0.1:7005 139 | header /api Access-Control-Allow-Origin * 140 | ``` 141 | 142 | When you run `caddy` in that directory, it will automatically find and use that Caddyfile. 143 | 144 | This simple file enables server push (via Link headers), allows directory browsing (for folders without an index file), hosts a WebSocket echo server at /echo, serves clean URLs, logs requests to an access log, proxies all API requests to a backend on port 7005, and adds the coveted `Access-Control-Allow-Origin: *` header for all responses from the API. 145 | 146 | Wow! Caddy can do a lot with just a few lines. 147 | 148 | ### Doing more with Caddy 149 | 150 | To host multiple sites and do more with the Caddyfile, please see the [Caddyfile tutorial](https://caddyserver.com/tutorial/caddyfile). 151 | 152 | Sites with qualifying hostnames are served over [HTTPS by default](https://caddyserver.com/docs/automatic-https). 153 | 154 | Caddy has a nice little command line interface. Run `caddy -h` to view basic help or see the [CLI documentation](https://caddyserver.com/docs/cli) for details. 155 | 156 | 157 | ## Running in Production 158 | 159 | Caddy is production-ready if you find it to be a good fit for your site and workflow. 160 | 161 | **Running as root:** We advise against this. You can still listen on ports < 1024 on Linux using setcap like so: `sudo setcap cap_net_bind_service=+ep ./caddy` 162 | 163 | The Caddy project does not officially maintain any system-specific integrations nor suggest how to administer your own system. But your download file includes [unofficial resources](https://github.com/caddyserver/caddy/tree/master/dist/init) contributed by the community that you may find helpful for running Caddy in production. 164 | 165 | How you choose to run Caddy is up to you. Many users are satisfied with `nohup caddy &`. Others use `screen`. Users who need Caddy to come back up after reboots either do so in the script that caused the reboot, add a command to an init script, or configure a service with their OS. 166 | 167 | If you have questions or concerns about Caddy' underlying crypto implementations, consult Go's [crypto packages](https://golang.org/pkg/crypto), starting with their documentation, then issues, then the code itself; as Caddy uses mainly those libraries. 168 | 169 | 170 | ## Contributing 171 | 172 | **[Join our forum](https://caddy.community) where you can chat with other Caddy users and developers!** To get familiar with the code base, try [Caddy code search on Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy/)! 173 | 174 | Please see our [contributing guidelines](https://github.com/caddyserver/caddy/blob/master/.github/CONTRIBUTING.md) for instructions. If you want to write a plugin, check out the [developer wiki](https://github.com/caddyserver/caddy/wiki). 175 | 176 | We use GitHub issues and pull requests only for discussing bug reports and the development of specific changes. We welcome all other topics on the [forum](https://caddy.community)! 177 | 178 | If you want to contribute to the documentation, please [submit an issue](https://github.com/caddyserver/caddy/issues/new) describing the change that should be made. 179 | 180 | ### Good First Issue 181 | 182 | If you are looking for somewhere to start and would like to help out by working on an existing issue, take a look at our [`Good First Issue`](https://github.com/caddyserver/caddy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) tag 183 | 184 | Thanks for making Caddy -- and the Web -- better! 185 | 186 | 187 | ## Donors 188 | 189 | - [DigitalOcean](https://m.do.co/c/6d7bdafccf96) is hosting the Caddy project. 190 | - [DNSimple](https://dnsimple.link/resolving-caddy) provides DNS services for Caddy's sites. 191 | - [DNS Spy](https://dnsspy.io) keeps an eye on Caddy's DNS properties. 192 | 193 | We thank them for their services. **If you want to help keep Caddy free, please [become a sponsor](https://github.com/sponsors/mholt)!** 194 | 195 | 196 | ## About the Project 197 | 198 | Caddy was born out of the need for a "batteries-included" web server that runs anywhere and doesn't have to take its configuration with it. Caddy took inspiration from [spark](https://github.com/rif/spark), [nginx](https://github.com/nginx/nginx), lighttpd, 199 | [Websocketd](https://github.com/joewalnes/websocketd) and [Vagrant](https://www.vagrantup.com/), which provides a pleasant mixture of features from each of them. 200 | 201 | **The name "Caddy" is trademarked:** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". See [brand guidelines](https://caddyserver.com/brand). Caddy is a registered trademark of Light Code Labs, LLC. 202 | 203 | *Author on Twitter: [@mholt6](https://twitter.com/mholt6)* 204 | -------------------------------------------------------------------------------- /caddyfile/dispenser_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "reflect" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | func TestDispenser_Val_Next(t *testing.T) { 24 | input := `host:port 25 | dir1 arg1 26 | dir2 arg2 arg3 27 | dir3` 28 | d := NewDispenser("Testfile", strings.NewReader(input)) 29 | 30 | if val := d.Val(); val != "" { 31 | t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val) 32 | } 33 | 34 | assertNext := func(shouldLoad bool, expectedCursor int, expectedVal string) { 35 | if loaded := d.Next(); loaded != shouldLoad { 36 | t.Errorf("Next(): Expected %v but got %v instead (val '%s')", shouldLoad, loaded, d.Val()) 37 | } 38 | if d.cursor != expectedCursor { 39 | t.Errorf("Expected cursor to be %d, but was %d", expectedCursor, d.cursor) 40 | } 41 | if d.nesting != 0 { 42 | t.Errorf("Nesting should be 0, was %d instead", d.nesting) 43 | } 44 | if val := d.Val(); val != expectedVal { 45 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val) 46 | } 47 | } 48 | 49 | assertNext(true, 0, "host:port") 50 | assertNext(true, 1, "dir1") 51 | assertNext(true, 2, "arg1") 52 | assertNext(true, 3, "dir2") 53 | assertNext(true, 4, "arg2") 54 | assertNext(true, 5, "arg3") 55 | assertNext(true, 6, "dir3") 56 | // Note: This next test simply asserts existing behavior. 57 | // If desired, we may wish to empty the token value after 58 | // reading past the EOF. Open an issue if you want this change. 59 | assertNext(false, 6, "dir3") 60 | } 61 | 62 | func TestDispenser_NextArg(t *testing.T) { 63 | input := `dir1 arg1 64 | dir2 arg2 arg3 65 | dir3` 66 | d := NewDispenser("Testfile", strings.NewReader(input)) 67 | 68 | assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) { 69 | if d.Next() != shouldLoad { 70 | t.Errorf("Next(): Should load token but got false instead (val: '%s')", d.Val()) 71 | } 72 | if d.cursor != expectedCursor { 73 | t.Errorf("Next(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor) 74 | } 75 | if val := d.Val(); val != expectedVal { 76 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val) 77 | } 78 | } 79 | 80 | assertNextArg := func(expectedVal string, loadAnother bool, expectedCursor int) { 81 | if !d.NextArg() { 82 | t.Error("NextArg(): Should load next argument but got false instead") 83 | } 84 | if d.cursor != expectedCursor { 85 | t.Errorf("NextArg(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor) 86 | } 87 | if val := d.Val(); val != expectedVal { 88 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val) 89 | } 90 | if !loadAnother { 91 | if d.NextArg() { 92 | t.Fatalf("NextArg(): Should NOT load another argument, but got true instead (val: '%s')", d.Val()) 93 | } 94 | if d.cursor != expectedCursor { 95 | t.Errorf("NextArg(): Expected cursor to remain at %d, but it was %d", expectedCursor, d.cursor) 96 | } 97 | } 98 | } 99 | 100 | assertNext(true, "dir1", 0) 101 | assertNextArg("arg1", false, 1) 102 | assertNext(true, "dir2", 2) 103 | assertNextArg("arg2", true, 3) 104 | assertNextArg("arg3", false, 4) 105 | assertNext(true, "dir3", 5) 106 | assertNext(false, "dir3", 5) 107 | } 108 | 109 | func TestDispenser_NextLine(t *testing.T) { 110 | input := `host:port 111 | dir1 arg1 112 | dir2 arg2 arg3` 113 | d := NewDispenser("Testfile", strings.NewReader(input)) 114 | 115 | assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) { 116 | if d.NextLine() != shouldLoad { 117 | t.Errorf("NextLine(): Should load token but got false instead (val: '%s')", d.Val()) 118 | } 119 | if d.cursor != expectedCursor { 120 | t.Errorf("NextLine(): Expected cursor to be %d, instead was %d", expectedCursor, d.cursor) 121 | } 122 | if val := d.Val(); val != expectedVal { 123 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val) 124 | } 125 | } 126 | 127 | assertNextLine(true, "host:port", 0) 128 | assertNextLine(true, "dir1", 1) 129 | assertNextLine(false, "dir1", 1) 130 | d.Next() // arg1 131 | assertNextLine(true, "dir2", 3) 132 | assertNextLine(false, "dir2", 3) 133 | d.Next() // arg2 134 | assertNextLine(false, "arg2", 4) 135 | d.Next() // arg3 136 | assertNextLine(false, "arg3", 5) 137 | } 138 | 139 | func TestDispenser_NextBlock(t *testing.T) { 140 | input := `foobar1 { 141 | sub1 arg1 142 | sub2 143 | } 144 | foobar2 { 145 | }` 146 | d := NewDispenser("Testfile", strings.NewReader(input)) 147 | 148 | assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) { 149 | if loaded := d.NextBlock(); loaded != shouldLoad { 150 | t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded) 151 | } 152 | if d.cursor != expectedCursor { 153 | t.Errorf("NextBlock(): Expected cursor to be %d, was %d", expectedCursor, d.cursor) 154 | } 155 | if d.nesting != expectedNesting { 156 | t.Errorf("NextBlock(): Nesting should be %d, not %d", expectedNesting, d.nesting) 157 | } 158 | } 159 | 160 | assertNextBlock(false, -1, 0) 161 | d.Next() // foobar1 162 | assertNextBlock(true, 2, 1) 163 | assertNextBlock(true, 3, 1) 164 | assertNextBlock(true, 4, 1) 165 | assertNextBlock(false, 5, 0) 166 | d.Next() // foobar2 167 | assertNextBlock(false, 8, 0) // empty block is as if it didn't exist 168 | } 169 | 170 | func TestDispenser_NextBlock_MakesProgressOrStopsOnEOF(t *testing.T) { 171 | // Unclosed block should not cause NextBlock to loop without advancing 172 | input := `foobar { 173 | sub1 arg1` 174 | d := NewDispenser("Testfile", strings.NewReader(input)) 175 | 176 | if !d.Next() { // foobar 177 | t.Fatal("expected first token") 178 | } 179 | 180 | if !d.NextBlock() { 181 | t.Fatal("expected to enter block with NextBlock") 182 | } 183 | 184 | // Iterate, ensuring each true result advanced the cursor, 185 | // and that we terminate within a reasonable number of steps 186 | const maxIters = 10 187 | prevCursor := d.cursor 188 | for i := 0; i < maxIters; i++ { 189 | ok := d.NextBlock() 190 | if !ok { 191 | return 192 | } 193 | if d.cursor == prevCursor { 194 | t.Fatalf("NextBlock(): did not advance cursor (stuck at %d) — potential infinite loop", d.cursor) 195 | } 196 | prevCursor = d.cursor 197 | } 198 | t.Fatalf("NextBlock(): exceeded %d iterations without terminating", maxIters) 199 | } 200 | 201 | func TestDispenser_Args(t *testing.T) { 202 | var s1, s2, s3 string 203 | input := `dir1 arg1 arg2 arg3 204 | dir2 arg4 arg5 205 | dir3 arg6 arg7 206 | dir4` 207 | d := NewDispenser("Testfile", strings.NewReader(input)) 208 | 209 | d.Next() // dir1 210 | 211 | // As many strings as arguments 212 | if all := d.Args(&s1, &s2, &s3); !all { 213 | t.Error("Args(): Expected true, got false") 214 | } 215 | if s1 != "arg1" { 216 | t.Errorf("Args(): Expected s1 to be 'arg1', got '%s'", s1) 217 | } 218 | if s2 != "arg2" { 219 | t.Errorf("Args(): Expected s2 to be 'arg2', got '%s'", s2) 220 | } 221 | if s3 != "arg3" { 222 | t.Errorf("Args(): Expected s3 to be 'arg3', got '%s'", s3) 223 | } 224 | 225 | d.Next() // dir2 226 | 227 | // More strings than arguments 228 | if all := d.Args(&s1, &s2, &s3); all { 229 | t.Error("Args(): Expected false, got true") 230 | } 231 | if s1 != "arg4" { 232 | t.Errorf("Args(): Expected s1 to be 'arg4', got '%s'", s1) 233 | } 234 | if s2 != "arg5" { 235 | t.Errorf("Args(): Expected s2 to be 'arg5', got '%s'", s2) 236 | } 237 | if s3 != "arg3" { 238 | t.Errorf("Args(): Expected s3 to be unchanged ('arg3'), instead got '%s'", s3) 239 | } 240 | 241 | // (quick cursor check just for kicks and giggles) 242 | if d.cursor != 6 { 243 | t.Errorf("Cursor should be 6, but is %d", d.cursor) 244 | } 245 | 246 | d.Next() // dir3 247 | 248 | // More arguments than strings 249 | if all := d.Args(&s1); !all { 250 | t.Error("Args(): Expected true, got false") 251 | } 252 | if s1 != "arg6" { 253 | t.Errorf("Args(): Expected s1 to be 'arg6', got '%s'", s1) 254 | } 255 | 256 | d.Next() // dir4 257 | 258 | // No arguments or strings 259 | if all := d.Args(); !all { 260 | t.Error("Args(): Expected true, got false") 261 | } 262 | 263 | // No arguments but at least one string 264 | if all := d.Args(&s1); all { 265 | t.Error("Args(): Expected false, got true") 266 | } 267 | } 268 | 269 | func TestDispenser_RemainingArgs(t *testing.T) { 270 | input := `dir1 arg1 arg2 arg3 271 | dir2 arg4 arg5 272 | dir3 arg6 { arg7 273 | dir4` 274 | d := NewDispenser("Testfile", strings.NewReader(input)) 275 | 276 | d.Next() // dir1 277 | 278 | args := d.RemainingArgs() 279 | if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(args, expected) { 280 | t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args) 281 | } 282 | 283 | d.Next() // dir2 284 | 285 | args = d.RemainingArgs() 286 | if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(args, expected) { 287 | t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args) 288 | } 289 | 290 | d.Next() // dir3 291 | 292 | args = d.RemainingArgs() 293 | if expected := []string{"arg6"}; !reflect.DeepEqual(args, expected) { 294 | t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args) 295 | } 296 | 297 | d.Next() // { 298 | d.Next() // arg7 299 | d.Next() // dir4 300 | 301 | args = d.RemainingArgs() 302 | if len(args) != 0 { 303 | t.Errorf("RemainingArgs(): Expected %v, got %v", []string{}, args) 304 | } 305 | } 306 | 307 | func TestDispenser_ArgErr_Err(t *testing.T) { 308 | input := `dir1 { 309 | } 310 | dir2 arg1 arg2` 311 | d := NewDispenser("Testfile", strings.NewReader(input)) 312 | 313 | d.cursor = 1 // { 314 | 315 | if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "{") { 316 | t.Errorf("ArgErr(): Expected an error message with { in it, but got '%v'", err) 317 | } 318 | 319 | d.cursor = 5 // arg2 320 | 321 | if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "arg2") { 322 | t.Errorf("ArgErr(): Expected an error message with 'arg2' in it; got '%v'", err) 323 | } 324 | 325 | err := d.Err("foobar") 326 | if err == nil { 327 | t.Fatalf("Err(): Expected an error, got nil") 328 | } 329 | 330 | if !strings.Contains(err.Error(), "Testfile:3") { 331 | t.Errorf("Expected error message with filename:line in it; got '%v'", err) 332 | } 333 | 334 | if !strings.Contains(err.Error(), "foobar") { 335 | t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err) 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /plugins.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddy 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "net" 21 | "sort" 22 | "sync" 23 | 24 | "github.com/coredns/caddy/caddyfile" 25 | ) 26 | 27 | // These are all the registered plugins. 28 | var ( 29 | // serverTypes is a map of registered server types. 30 | serverTypes = make(map[string]ServerType) 31 | 32 | // plugins is a map of server type to map of plugin name to 33 | // Plugin. These are the "general" plugins that may or may 34 | // not be associated with a specific server type. If it's 35 | // applicable to multiple server types or the server type is 36 | // irrelevant, the key is empty string (""). But all plugins 37 | // must have a name. 38 | plugins = make(map[string]map[string]Plugin) 39 | 40 | // eventHooks is a map of hook name to Hook. All hooks plugins 41 | // must have a name. 42 | eventHooks = &sync.Map{} 43 | 44 | // parsingCallbacks maps server type to map of directive 45 | // to list of callback functions. These aren't really 46 | // plugins on their own, but are often registered from 47 | // plugins. 48 | parsingCallbacks = make(map[string]map[string][]ParsingCallback) 49 | 50 | // caddyfileLoaders is the list of all Caddyfile loaders 51 | // in registration order. 52 | caddyfileLoaders []caddyfileLoader 53 | ) 54 | 55 | // DescribePlugins returns a string describing the registered plugins. 56 | func DescribePlugins() string { 57 | pl := ListPlugins() 58 | 59 | str := "" 60 | for _, name := range pl["others"] { 61 | if len(name) > 3 { 62 | str += name[4:] + "\n" // drop dns. prefix caddy adds 63 | } else { 64 | str += name + "\n" 65 | } 66 | } 67 | 68 | return str 69 | } 70 | 71 | // ListPlugins makes a list of the registered plugins, 72 | // keyed by plugin type. 73 | func ListPlugins() map[string][]string { 74 | p := make(map[string][]string) 75 | 76 | // server type plugins 77 | for name := range serverTypes { 78 | p["server_types"] = append(p["server_types"], name) 79 | } 80 | 81 | // caddyfile loaders in registration order 82 | for _, loader := range caddyfileLoaders { 83 | p["caddyfile_loaders"] = append(p["caddyfile_loaders"], loader.name) 84 | } 85 | if defaultCaddyfileLoader.name != "" { 86 | p["caddyfile_loaders"] = append(p["caddyfile_loaders"], defaultCaddyfileLoader.name) 87 | } 88 | 89 | // List the event hook plugins 90 | eventHooks.Range(func(k, _ interface{}) bool { 91 | p["event_hooks"] = append(p["event_hooks"], k.(string)) 92 | return true 93 | }) 94 | 95 | // alphabetize the rest of the plugins 96 | var others []string 97 | for stype, stypePlugins := range plugins { 98 | for name := range stypePlugins { 99 | var s string 100 | if stype != "" { 101 | s = stype + "." 102 | } 103 | s += name 104 | others = append(others, s) 105 | } 106 | } 107 | 108 | sort.Strings(others) 109 | for _, name := range others { 110 | p["others"] = append(p["others"], name) 111 | } 112 | 113 | return p 114 | } 115 | 116 | // ValidDirectives returns the list of all directives that are 117 | // recognized for the server type serverType. However, not all 118 | // directives may be installed. This makes it possible to give 119 | // more helpful error messages, like "did you mean ..." or 120 | // "maybe you need to plug in ...". 121 | func ValidDirectives(serverType string) []string { 122 | stype, err := getServerType(serverType) 123 | if err != nil { 124 | return nil 125 | } 126 | return stype.Directives() 127 | } 128 | 129 | // ServerListener pairs a server to its listener and/or packetconn. 130 | type ServerListener struct { 131 | server Server 132 | listener net.Listener 133 | packet net.PacketConn 134 | } 135 | 136 | // LocalAddr returns the local network address of the packetconn. It returns 137 | // nil when it is not set. 138 | func (s ServerListener) LocalAddr() net.Addr { 139 | if s.packet == nil { 140 | return nil 141 | } 142 | return s.packet.LocalAddr() 143 | } 144 | 145 | // Addr returns the listener's network address. It returns nil when it is 146 | // not set. 147 | func (s ServerListener) Addr() net.Addr { 148 | if s.listener == nil { 149 | return nil 150 | } 151 | return s.listener.Addr() 152 | } 153 | 154 | // Context is a type which carries a server type through 155 | // the load and setup phase; it maintains the state 156 | // between loading the Caddyfile, then executing its 157 | // directives, then making the servers for Caddy to 158 | // manage. Typically, such state involves configuration 159 | // structs, etc. 160 | type Context interface { 161 | // Called after the Caddyfile is parsed into server 162 | // blocks but before the directives are executed, 163 | // this method gives you an opportunity to inspect 164 | // the server blocks and prepare for the execution 165 | // of directives. Return the server blocks (which 166 | // you may modify, if desired) and an error, if any. 167 | // The first argument is the name or path to the 168 | // configuration file (Caddyfile). 169 | // 170 | // This function can be a no-op and simply return its 171 | // input if there is nothing to do here. 172 | InspectServerBlocks(string, []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) 173 | 174 | // This is what Caddy calls to make server instances. 175 | // By this time, all directives have been executed and, 176 | // presumably, the context has enough state to produce 177 | // server instances for Caddy to start. 178 | MakeServers() ([]Server, error) 179 | } 180 | 181 | // RegisterServerType registers a server type srv by its 182 | // name, typeName. 183 | func RegisterServerType(typeName string, srv ServerType) { 184 | if _, ok := serverTypes[typeName]; ok { 185 | panic("server type already registered") 186 | } 187 | serverTypes[typeName] = srv 188 | } 189 | 190 | // ServerType contains information about a server type. 191 | type ServerType struct { 192 | // Function that returns the list of directives, in 193 | // execution order, that are valid for this server 194 | // type. Directives should be one word if possible 195 | // and lower-cased. 196 | Directives func() []string 197 | 198 | // DefaultInput returns a default config input if none 199 | // is otherwise loaded. This is optional, but highly 200 | // recommended, otherwise a blank Caddyfile will be 201 | // used. 202 | DefaultInput func() Input 203 | 204 | // The function that produces a new server type context. 205 | // This will be called when a new Caddyfile is being 206 | // loaded, parsed, and executed independently of any 207 | // startup phases before this one. It's a way to keep 208 | // each set of server instances separate and to reduce 209 | // the amount of global state you need. 210 | NewContext func(inst *Instance) Context 211 | } 212 | 213 | // Plugin is a type which holds information about a plugin. 214 | type Plugin struct { 215 | // ServerType is the type of server this plugin is for. 216 | // Can be empty if not applicable, or if the plugin 217 | // can associate with any server type. 218 | ServerType string 219 | 220 | // Action is the plugin's setup function, if associated 221 | // with a directive in the Caddyfile. 222 | Action SetupFunc 223 | } 224 | 225 | // RegisterPlugin plugs in plugin. All plugins should register 226 | // themselves, even if they do not perform an action associated 227 | // with a directive. It is important for the process to know 228 | // which plugins are available. 229 | // 230 | // The plugin MUST have a name: lower case and one word. 231 | // If this plugin has an action, it must be the name of 232 | // the directive that invokes it. A name is always required 233 | // and must be unique for the server type. 234 | func RegisterPlugin(name string, plugin Plugin) { 235 | if name == "" { 236 | panic("plugin must have a name") 237 | } 238 | if _, ok := plugins[plugin.ServerType]; !ok { 239 | plugins[plugin.ServerType] = make(map[string]Plugin) 240 | } 241 | if _, dup := plugins[plugin.ServerType][name]; dup { 242 | panic("plugin named " + name + " already registered for server type " + plugin.ServerType) 243 | } 244 | plugins[plugin.ServerType][name] = plugin 245 | } 246 | 247 | // EventName represents the name of an event used with event hooks. 248 | type EventName string 249 | 250 | // Define names for the various events 251 | const ( 252 | StartupEvent EventName = "startup" 253 | ShutdownEvent = "shutdown" 254 | CertRenewEvent = "certrenew" 255 | InstanceStartupEvent = "instancestartup" 256 | InstanceRestartEvent = "instancerestart" 257 | ) 258 | 259 | // EventHook is a type which holds information about a startup hook plugin. 260 | type EventHook func(eventType EventName, eventInfo interface{}) error 261 | 262 | // RegisterEventHook plugs in hook. All the hooks should register themselves 263 | // and they must have a name. 264 | func RegisterEventHook(name string, hook EventHook) { 265 | if name == "" { 266 | panic("event hook must have a name") 267 | } 268 | _, dup := eventHooks.LoadOrStore(name, hook) 269 | if dup { 270 | panic("hook named " + name + " already registered") 271 | } 272 | } 273 | 274 | // EmitEvent executes the different hooks passing the EventType as an 275 | // argument. This is a blocking function. Hook developers should 276 | // use 'go' keyword if they don't want to block Caddy. 277 | func EmitEvent(event EventName, info interface{}) { 278 | eventHooks.Range(func(k, v interface{}) bool { 279 | err := v.(EventHook)(event, info) 280 | if err != nil { 281 | log.Printf("error on '%s' hook: %v", k.(string), err) 282 | } 283 | return true 284 | }) 285 | } 286 | 287 | // cloneEventHooks return a clone of the event hooks *sync.Map 288 | func cloneEventHooks() *sync.Map { 289 | c := &sync.Map{} 290 | eventHooks.Range(func(k, v interface{}) bool { 291 | c.Store(k, v) 292 | return true 293 | }) 294 | return c 295 | } 296 | 297 | // purgeEventHooks purges all event hooks from the map 298 | func purgeEventHooks() { 299 | eventHooks.Range(func(k, _ interface{}) bool { 300 | eventHooks.Delete(k) 301 | return true 302 | }) 303 | } 304 | 305 | // restoreEventHooks restores eventHooks with a provided *sync.Map 306 | func restoreEventHooks(m *sync.Map) { 307 | // Purge old event hooks 308 | purgeEventHooks() 309 | 310 | // Restore event hooks 311 | m.Range(func(k, v interface{}) bool { 312 | eventHooks.Store(k, v) 313 | return true 314 | }) 315 | } 316 | 317 | // ParsingCallback is a function that is called after 318 | // a directive's setup functions have been executed 319 | // for all the server blocks. 320 | type ParsingCallback func(Context) error 321 | 322 | // RegisterParsingCallback registers callback to be called after 323 | // executing the directive afterDir for server type serverType. 324 | func RegisterParsingCallback(serverType, afterDir string, callback ParsingCallback) { 325 | if _, ok := parsingCallbacks[serverType]; !ok { 326 | parsingCallbacks[serverType] = make(map[string][]ParsingCallback) 327 | } 328 | parsingCallbacks[serverType][afterDir] = append(parsingCallbacks[serverType][afterDir], callback) 329 | } 330 | 331 | // SetupFunc is used to set up a plugin, or in other words, 332 | // execute a directive. It will be called once per key for 333 | // each server block it appears in. 334 | type SetupFunc func(c *Controller) error 335 | 336 | // DirectiveAction gets the action for directive dir of 337 | // server type serverType. 338 | func DirectiveAction(serverType, dir string) (SetupFunc, error) { 339 | if stypePlugins, ok := plugins[serverType]; ok { 340 | if plugin, ok := stypePlugins[dir]; ok { 341 | return plugin.Action, nil 342 | } 343 | } 344 | if genericPlugins, ok := plugins[""]; ok { 345 | if plugin, ok := genericPlugins[dir]; ok { 346 | return plugin.Action, nil 347 | } 348 | } 349 | return nil, fmt.Errorf("no action found for directive '%s' with server type '%s' (missing a plugin?)", 350 | dir, serverType) 351 | } 352 | 353 | // Loader is a type that can load a Caddyfile. 354 | // It is passed the name of the server type. 355 | // It returns an error only if something went 356 | // wrong, not simply if there is no Caddyfile 357 | // for this loader to load. 358 | // 359 | // A Loader should only load the Caddyfile if 360 | // a certain condition or requirement is met, 361 | // as returning a non-nil Input value along with 362 | // another Loader will result in an error. 363 | // In other words, loading the Caddyfile must 364 | // be deliberate & deterministic, not haphazard. 365 | // 366 | // The exception is the default Caddyfile loader, 367 | // which will be called only if no other Caddyfile 368 | // loaders return a non-nil Input. The default 369 | // loader may always return an Input value. 370 | type Loader interface { 371 | Load(serverType string) (Input, error) 372 | } 373 | 374 | // LoaderFunc is a convenience type similar to http.HandlerFunc 375 | // that allows you to use a plain function as a Load() method. 376 | type LoaderFunc func(serverType string) (Input, error) 377 | 378 | // Load loads a Caddyfile. 379 | func (lf LoaderFunc) Load(serverType string) (Input, error) { 380 | return lf(serverType) 381 | } 382 | 383 | // RegisterCaddyfileLoader registers loader named name. 384 | func RegisterCaddyfileLoader(name string, loader Loader) { 385 | caddyfileLoaders = append(caddyfileLoaders, caddyfileLoader{name: name, loader: loader}) 386 | } 387 | 388 | // SetDefaultCaddyfileLoader registers loader by name 389 | // as the default Caddyfile loader if no others produce 390 | // a Caddyfile. If another Caddyfile loader has already 391 | // been set as the default, this replaces it. 392 | // 393 | // Do not call RegisterCaddyfileLoader on the same 394 | // loader; that would be redundant. 395 | func SetDefaultCaddyfileLoader(name string, loader Loader) { 396 | defaultCaddyfileLoader = caddyfileLoader{name: name, loader: loader} 397 | } 398 | 399 | // loadCaddyfileInput iterates the registered Caddyfile loaders 400 | // and, if needed, calls the default loader, to load a Caddyfile. 401 | // It is an error if any of the loaders return an error or if 402 | // more than one loader returns a Caddyfile. 403 | func loadCaddyfileInput(serverType string) (Input, error) { 404 | var loadedBy string 405 | var caddyfileToUse Input 406 | for _, l := range caddyfileLoaders { 407 | cdyfile, err := l.loader.Load(serverType) 408 | if err != nil { 409 | return nil, fmt.Errorf("loading Caddyfile via %s: %v", l.name, err) 410 | } 411 | if cdyfile != nil { 412 | if caddyfileToUse != nil { 413 | return nil, fmt.Errorf("Caddyfile loaded multiple times; first by %s, then by %s", loadedBy, l.name) 414 | } 415 | loaderUsed = l 416 | caddyfileToUse = cdyfile 417 | loadedBy = l.name 418 | } 419 | } 420 | if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil { 421 | cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType) 422 | if err != nil { 423 | return nil, err 424 | } 425 | if cdyfile != nil { 426 | loaderUsed = defaultCaddyfileLoader 427 | caddyfileToUse = cdyfile 428 | } 429 | } 430 | return caddyfileToUse, nil 431 | } 432 | 433 | // OnProcessExit is a list of functions to run when the process 434 | // exits -- they are ONLY for cleanup and should not block, 435 | // return errors, or do anything fancy. They will be run with 436 | // every signal, even if "shutdown callbacks" are not executed. 437 | // This variable must only be modified in the main goroutine 438 | // from init() functions. 439 | var OnProcessExit []func() 440 | 441 | // caddyfileLoader pairs the name of a loader to the loader. 442 | type caddyfileLoader struct { 443 | name string 444 | loader Loader 445 | } 446 | 447 | var ( 448 | defaultCaddyfileLoader caddyfileLoader // the default loader if all else fail 449 | loaderUsed caddyfileLoader // the loader that was used (relevant for reloads) 450 | ) 451 | -------------------------------------------------------------------------------- /caddyfile/parse.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "io" 19 | "log" 20 | "os" 21 | "path/filepath" 22 | "strings" 23 | ) 24 | 25 | // Parse parses the input just enough to group tokens, in 26 | // order, by server block. No further parsing is performed. 27 | // Server blocks are returned in the order in which they appear. 28 | // Directives that do not appear in validDirectives will cause 29 | // an error. If you do not want to check for valid directives, 30 | // pass in nil instead. 31 | func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) { 32 | p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives} 33 | return p.parseAll() 34 | } 35 | 36 | // allTokens lexes the entire input, but does not parse it. 37 | // It returns all the tokens from the input, unstructured 38 | // and in order. 39 | func allTokens(input io.Reader) ([]Token, error) { 40 | l := new(lexer) 41 | err := l.load(input) 42 | if err != nil { 43 | return nil, err 44 | } 45 | var tokens []Token 46 | for l.next() { 47 | tokens = append(tokens, l.token) 48 | } 49 | return tokens, nil 50 | } 51 | 52 | type parser struct { 53 | Dispenser 54 | block ServerBlock // current server block being parsed 55 | validDirectives []string // a directive must be valid or it's an error 56 | eof bool // if we encounter a valid EOF in a hard place 57 | definedSnippets map[string][]Token 58 | snippetExpansions int // counts snippet imports expanded during this parse 59 | fileExpansions int // counts file/glob imports expanded during this parse 60 | } 61 | 62 | // maxSnippetExpansions is a hard cap to prevent excessively deep or cyclic snippet imports. 63 | // set as a variable to allow modifications for testing 64 | var maxSnippetExpansions = 10000 65 | 66 | // maxFileExpansions is a hard cap to prevent excessively deep or cyclic file imports. 67 | // set as a variable to allow modifications for testing 68 | var maxFileExpansions = 100000 69 | 70 | func (p *parser) parseAll() ([]ServerBlock, error) { 71 | var blocks []ServerBlock 72 | 73 | for p.Next() { 74 | err := p.parseOne() 75 | if err != nil { 76 | return blocks, err 77 | } 78 | if len(p.block.Keys) > 0 { 79 | blocks = append(blocks, p.block) 80 | } 81 | } 82 | 83 | return blocks, nil 84 | } 85 | 86 | func (p *parser) parseOne() error { 87 | p.block = ServerBlock{Tokens: make(map[string][]Token)} 88 | 89 | return p.begin() 90 | } 91 | 92 | func (p *parser) begin() error { 93 | if len(p.tokens) == 0 { 94 | return nil 95 | } 96 | 97 | err := p.addresses() 98 | 99 | if err != nil { 100 | return err 101 | } 102 | 103 | if p.eof { 104 | // this happens if the Caddyfile consists of only 105 | // a line of addresses and nothing else 106 | return nil 107 | } 108 | 109 | if ok, name := p.isSnippet(); ok { 110 | if p.definedSnippets == nil { 111 | p.definedSnippets = map[string][]Token{} 112 | } 113 | if _, found := p.definedSnippets[name]; found { 114 | return p.Errf("redeclaration of previously declared snippet %s", name) 115 | } 116 | // consume all tokens til matched close brace 117 | tokens, err := p.snippetTokens() 118 | if err != nil { 119 | return err 120 | } 121 | // minimal guard: detect trivial self-import in snippet body 122 | for i := 0; i+1 < len(tokens); i++ { 123 | if tokens[i].Text == "import" { 124 | // Only consider it an import directive if at start of a line 125 | atLineStart := i == 0 || tokens[i-1].File != tokens[i].File || tokens[i-1].Line != tokens[i].Line 126 | if atLineStart && replaceEnvVars(tokens[i+1].Text) == name { 127 | return p.Errf("maximum snippet import depth (%d) exceeded", maxSnippetExpansions) 128 | } 129 | } 130 | } 131 | p.definedSnippets[name] = tokens 132 | // empty block keys so we don't save this block as a real server. 133 | p.block.Keys = nil 134 | return nil 135 | } 136 | 137 | return p.blockContents() 138 | } 139 | 140 | func (p *parser) addresses() error { 141 | var expectingAnother bool 142 | 143 | for { 144 | tkn := replaceEnvVars(p.Val()) 145 | 146 | // special case: import directive replaces tokens during parse-time 147 | if tkn == "import" && p.isNewLine() { 148 | err := p.doImport() 149 | if err != nil { 150 | return err 151 | } 152 | continue 153 | } 154 | 155 | // Open brace definitely indicates end of addresses 156 | if tkn == "{" { 157 | if expectingAnother { 158 | return p.Errf("Expected another address but had '%s' - check for extra comma", tkn) 159 | } 160 | break 161 | } 162 | 163 | if tkn != "" { // empty token possible if user typed "" 164 | // Trailing comma indicates another address will follow, which 165 | // may possibly be on the next line 166 | if tkn[len(tkn)-1] == ',' { 167 | tkn = tkn[:len(tkn)-1] 168 | expectingAnother = true 169 | } else { 170 | expectingAnother = false // but we may still see another one on this line 171 | } 172 | 173 | p.block.Keys = append(p.block.Keys, tkn) 174 | } 175 | 176 | // Advance token and possibly break out of loop or return error 177 | hasNext := p.Next() 178 | if expectingAnother && !hasNext { 179 | return p.EOFErr() 180 | } 181 | if !hasNext { 182 | p.eof = true 183 | break // EOF 184 | } 185 | if !expectingAnother && p.isNewLine() { 186 | break 187 | } 188 | } 189 | 190 | return nil 191 | } 192 | 193 | func (p *parser) blockContents() error { 194 | errOpenCurlyBrace := p.openCurlyBrace() 195 | if errOpenCurlyBrace != nil { 196 | // single-server configs don't need curly braces 197 | p.cursor-- 198 | } 199 | 200 | err := p.directives() 201 | if err != nil { 202 | return err 203 | } 204 | 205 | // Only look for close curly brace if there was an opening 206 | if errOpenCurlyBrace == nil { 207 | err = p.closeCurlyBrace() 208 | if err != nil { 209 | return err 210 | } 211 | } 212 | 213 | return nil 214 | } 215 | 216 | // directives parses through all the lines for directives 217 | // and it expects the next token to be the first 218 | // directive. It goes until EOF or closing curly brace 219 | // which ends the server block. 220 | func (p *parser) directives() error { 221 | for p.Next() { 222 | // end of server block 223 | if p.Val() == "}" { 224 | break 225 | } 226 | 227 | // special case: import directive replaces tokens during parse-time 228 | if p.Val() == "import" { 229 | err := p.doImport() 230 | if err != nil { 231 | return err 232 | } 233 | p.cursor-- // cursor is advanced when we continue, so roll back one more 234 | continue 235 | } 236 | 237 | // normal case: parse a directive on this line 238 | if err := p.directive(); err != nil { 239 | return err 240 | } 241 | } 242 | return nil 243 | } 244 | 245 | // doImport swaps out the import directive and its argument 246 | // (a total of 2 tokens) with the tokens in the specified file 247 | // or globbing pattern. When the function returns, the cursor 248 | // is on the token before where the import directive was. In 249 | // other words, call Next() to access the first token that was 250 | // imported. 251 | func (p *parser) doImport() error { 252 | // syntax checks 253 | if !p.NextArg() { 254 | return p.ArgErr() 255 | } 256 | importPattern := replaceEnvVars(p.Val()) 257 | if importPattern == "" { 258 | return p.Err("Import requires a non-empty filepath") 259 | } 260 | if p.NextArg() { 261 | return p.Err("Import takes only one argument (glob pattern or file)") 262 | } 263 | // splice out the import directive and its argument (2 tokens total) 264 | tokensBefore := p.tokens[:p.cursor-1] 265 | tokensAfter := p.tokens[p.cursor+1:] 266 | var importedTokens []Token 267 | 268 | // first check snippets. Count expansion and enforce cap. 269 | if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil { 270 | if p.snippetExpansions >= maxSnippetExpansions { 271 | return p.Errf("maximum snippet import depth (%d) exceeded", maxSnippetExpansions) 272 | } 273 | p.snippetExpansions++ 274 | importedTokens = p.definedSnippets[importPattern] 275 | } else { 276 | if p.fileExpansions >= maxFileExpansions { 277 | return p.Errf("maximum file import depth (%d) exceeded", maxSnippetExpansions) 278 | } 279 | p.fileExpansions++ 280 | // make path relative to the file of the _token_ being processed rather 281 | // than current working directory (issue #867) and then use glob to get 282 | // list of matching filenames 283 | absFile, err := filepath.Abs(p.Dispenser.File()) 284 | if err != nil { 285 | return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err) 286 | } 287 | 288 | var matches []string 289 | var globPattern string 290 | if !filepath.IsAbs(importPattern) { 291 | globPattern = filepath.Join(filepath.Dir(absFile), importPattern) 292 | } else { 293 | globPattern = importPattern 294 | } 295 | if strings.Count(globPattern, "*") > 1 || strings.Count(globPattern, "?") > 1 || 296 | (strings.Contains(globPattern, "[") && strings.Contains(globPattern, "]")) { 297 | // See issue #2096 - a pattern with many glob expansions can hang for too long 298 | return p.Errf("Glob pattern may only contain one wildcard (*), but has others: %s", globPattern) 299 | } 300 | matches, err = filepath.Glob(globPattern) 301 | 302 | if err != nil { 303 | return p.Errf("Failed to use import pattern %s: %v", importPattern, err) 304 | } 305 | if len(matches) == 0 { 306 | if strings.ContainsAny(globPattern, "*?[]") { 307 | log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern) 308 | } else { 309 | return p.Errf("File to import not found: %s", importPattern) 310 | } 311 | } 312 | 313 | // collect all the imported tokens 314 | 315 | for _, importFile := range matches { 316 | newTokens, err := p.doSingleImport(importFile) 317 | if err != nil { 318 | return err 319 | } 320 | importedTokens = append(importedTokens, newTokens...) 321 | } 322 | } 323 | 324 | // splice the imported tokens in the place of the import statement 325 | // and rewind cursor so Next() will land on first imported token 326 | p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...) 327 | p.cursor-- 328 | 329 | return nil 330 | } 331 | 332 | // doSingleImport lexes the individual file at importFile and returns 333 | // its tokens or an error, if any. 334 | func (p *parser) doSingleImport(importFile string) ([]Token, error) { 335 | file, err := os.Open(importFile) 336 | if err != nil { 337 | return nil, p.Errf("Could not import %s: %v", importFile, err) 338 | } 339 | defer file.Close() 340 | 341 | if info, err := file.Stat(); err != nil { 342 | return nil, p.Errf("Could not import %s: %v", importFile, err) 343 | } else if info.IsDir() { 344 | return nil, p.Errf("Could not import %s: is a directory", importFile) 345 | } 346 | 347 | importedTokens, err := allTokens(file) 348 | if err != nil { 349 | return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err) 350 | } 351 | 352 | // Tack the file path onto these tokens so errors show the imported file's name 353 | // (we use full, absolute path to avoid bugs: issue #1892) 354 | filename, err := filepath.Abs(importFile) 355 | if err != nil { 356 | return nil, p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err) 357 | } 358 | for i := 0; i < len(importedTokens); i++ { 359 | importedTokens[i].File = filename 360 | } 361 | 362 | return importedTokens, nil 363 | } 364 | 365 | // directive collects tokens until the directive's scope 366 | // closes (either end of line or end of curly brace block). 367 | // It expects the currently-loaded token to be a directive 368 | // (or } that ends a server block). The collected tokens 369 | // are loaded into the current server block for later use 370 | // by directive setup functions. 371 | func (p *parser) directive() error { 372 | dir := replaceEnvVars(p.Val()) 373 | nesting := 0 374 | 375 | // TODO: More helpful error message ("did you mean..." or "maybe you need to install its server type") 376 | if !p.validDirective(dir) { 377 | return p.Errf("Unknown directive '%s'", dir) 378 | } 379 | 380 | // The directive itself is appended as a relevant token 381 | p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) 382 | 383 | for p.Next() { 384 | if p.Val() == "{" { 385 | nesting++ 386 | } else if p.isNewLine() && nesting == 0 { 387 | p.cursor-- // read too far 388 | break 389 | } else if p.Val() == "}" && nesting > 0 { 390 | nesting-- 391 | } else if p.Val() == "}" && nesting == 0 { 392 | return p.Err("Unexpected '}' because no matching opening brace") 393 | } else if p.Val() == "import" && p.isNewLine() { 394 | if err := p.doImport(); err != nil { 395 | return err 396 | } 397 | p.cursor-- // cursor is advanced when we continue, so roll back one more 398 | continue 399 | } 400 | p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text) 401 | p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor]) 402 | } 403 | 404 | if nesting > 0 { 405 | return p.EOFErr() 406 | } 407 | return nil 408 | } 409 | 410 | // openCurlyBrace expects the current token to be an 411 | // opening curly brace. This acts like an assertion 412 | // because it returns an error if the token is not 413 | // a opening curly brace. It does NOT advance the token. 414 | func (p *parser) openCurlyBrace() error { 415 | if p.Val() != "{" { 416 | return p.SyntaxErr("{") 417 | } 418 | return nil 419 | } 420 | 421 | // closeCurlyBrace expects the current token to be 422 | // a closing curly brace. This acts like an assertion 423 | // because it returns an error if the token is not 424 | // a closing curly brace. It does NOT advance the token. 425 | func (p *parser) closeCurlyBrace() error { 426 | if p.Val() != "}" { 427 | return p.SyntaxErr("}") 428 | } 429 | return nil 430 | } 431 | 432 | // validDirective returns true if dir is in p.validDirectives. 433 | func (p *parser) validDirective(dir string) bool { 434 | if p.validDirectives == nil { 435 | return true 436 | } 437 | for _, d := range p.validDirectives { 438 | if d == dir { 439 | return true 440 | } 441 | } 442 | return false 443 | } 444 | 445 | // replaceEnvVars replaces environment variables that appear in the token 446 | // and understands both the $UNIX and %WINDOWS% syntaxes. 447 | func replaceEnvVars(s string) string { 448 | s = replaceEnvReferences(s, "{%", "%}") 449 | s = replaceEnvReferences(s, "{$", "}") 450 | return s 451 | } 452 | 453 | // replaceEnvReferences performs the actual replacement of env variables 454 | // in s, given the placeholder start and placeholder end strings. 455 | func replaceEnvReferences(s, refStart, refEnd string) string { 456 | index := strings.Index(s, refStart) 457 | for index != -1 { 458 | endIndex := strings.Index(s[index:], refEnd) 459 | if endIndex == -1 { 460 | break 461 | } 462 | 463 | endIndex += index 464 | if endIndex > index+len(refStart) { 465 | ref := s[index : endIndex+len(refEnd)] 466 | s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1) 467 | } else { 468 | return s 469 | } 470 | index = strings.Index(s, refStart) 471 | } 472 | return s 473 | } 474 | 475 | // ServerBlock associates any number of keys (usually addresses 476 | // of some sort) with tokens (grouped by directive name). 477 | type ServerBlock struct { 478 | Keys []string 479 | Tokens map[string][]Token 480 | } 481 | 482 | func (p *parser) isSnippet() (bool, string) { 483 | keys := p.block.Keys 484 | // A snippet block is a single key with parens. Nothing else qualifies. 485 | if len(keys) == 1 && strings.HasPrefix(keys[0], "(") && strings.HasSuffix(keys[0], ")") { 486 | return true, strings.TrimSuffix(keys[0][1:], ")") 487 | } 488 | return false, "" 489 | } 490 | 491 | // read and store everything in a block for later replay. 492 | func (p *parser) snippetTokens() ([]Token, error) { 493 | // TODO: disallow imports in snippets for simplicity at import time 494 | // snippet must have curlies. 495 | err := p.openCurlyBrace() 496 | if err != nil { 497 | return nil, err 498 | } 499 | count := 1 500 | tokens := []Token{} 501 | for p.Next() { 502 | if p.Val() == "}" { 503 | count-- 504 | if count == 0 { 505 | break 506 | } 507 | } 508 | if p.Val() == "{" { 509 | count++ 510 | } 511 | tokens = append(tokens, p.tokens[p.cursor]) 512 | } 513 | // make sure we're matched up 514 | if count != 0 { 515 | return nil, p.SyntaxErr("}") 516 | } 517 | return tokens, nil 518 | } 519 | -------------------------------------------------------------------------------- /caddyfile/parse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package caddyfile 16 | 17 | import ( 18 | "fmt" 19 | "io/ioutil" 20 | "os" 21 | "path/filepath" 22 | "strings" 23 | "testing" 24 | ) 25 | 26 | func init() { 27 | // set a lower limit for testing only 28 | maxSnippetExpansions = 10 29 | maxFileExpansions = 10 30 | } 31 | 32 | func TestAllTokens(t *testing.T) { 33 | tests := []struct { 34 | name string 35 | input string 36 | expected []string 37 | }{ 38 | { 39 | name: "not-empty", 40 | input: "a b c\nd e", 41 | expected: []string{"a", "b", "c", "d", "e"}, 42 | }, { 43 | name: "empty", 44 | input: "", 45 | }, { 46 | name: "newline", 47 | input: "\n", 48 | }, { 49 | name: "space", 50 | input: " ", 51 | }, { 52 | name: "tab and newline", 53 | input: "\t\n", 54 | }, 55 | } 56 | 57 | for _, tt := range tests { 58 | t.Run(tt.name, func(t *testing.T) { 59 | tokens, err := allTokens(strings.NewReader(tt.input)) 60 | 61 | if err != nil { 62 | t.Fatalf("Expected no error, got %v", err) 63 | } 64 | if len(tokens) != len(tt.expected) { 65 | t.Fatalf("Expected %d tokens, got %d", len(tt.expected), len(tokens)) 66 | } 67 | 68 | for i, val := range tt.expected { 69 | if tokens[i].Text != val { 70 | t.Errorf("Token %d should be '%s' but was '%s'", i, val, tokens[i].Text) 71 | } 72 | } 73 | }) 74 | } 75 | } 76 | 77 | func TestParseOneAndImport(t *testing.T) { 78 | testParseOne := func(input string) (ServerBlock, error) { 79 | p := testParser(input) 80 | p.Next() // parseOne doesn't call Next() to start, so we must 81 | err := p.parseOne() 82 | return p.block, err 83 | } 84 | 85 | for i, test := range []struct { 86 | input string 87 | shouldErr bool 88 | keys []string 89 | tokens map[string]int // map of directive name to number of tokens expected 90 | }{ 91 | {`localhost`, false, []string{ 92 | "localhost", 93 | }, map[string]int{}}, 94 | 95 | {`localhost 96 | dir1`, false, []string{ 97 | "localhost", 98 | }, map[string]int{ 99 | "dir1": 1, 100 | }}, 101 | 102 | {`localhost:1234 103 | dir1 foo bar`, false, []string{ 104 | "localhost:1234", 105 | }, map[string]int{ 106 | "dir1": 3, 107 | }}, 108 | 109 | {`localhost { 110 | dir1 111 | }`, false, []string{ 112 | "localhost", 113 | }, map[string]int{ 114 | "dir1": 1, 115 | }}, 116 | 117 | {`localhost:1234 { 118 | dir1 foo bar 119 | dir2 120 | }`, false, []string{ 121 | "localhost:1234", 122 | }, map[string]int{ 123 | "dir1": 3, 124 | "dir2": 1, 125 | }}, 126 | 127 | {`http://localhost https://localhost 128 | dir1 foo bar`, false, []string{ 129 | "http://localhost", 130 | "https://localhost", 131 | }, map[string]int{ 132 | "dir1": 3, 133 | }}, 134 | 135 | {`http://localhost https://localhost { 136 | dir1 foo bar 137 | }`, false, []string{ 138 | "http://localhost", 139 | "https://localhost", 140 | }, map[string]int{ 141 | "dir1": 3, 142 | }}, 143 | 144 | {`http://localhost, https://localhost { 145 | dir1 foo bar 146 | }`, false, []string{ 147 | "http://localhost", 148 | "https://localhost", 149 | }, map[string]int{ 150 | "dir1": 3, 151 | }}, 152 | 153 | {`http://localhost, { 154 | }`, true, []string{ 155 | "http://localhost", 156 | }, map[string]int{}}, 157 | 158 | {`host1:80, http://host2.com 159 | dir1 foo bar 160 | dir2 baz`, false, []string{ 161 | "host1:80", 162 | "http://host2.com", 163 | }, map[string]int{ 164 | "dir1": 3, 165 | "dir2": 2, 166 | }}, 167 | 168 | {`http://host1.com, 169 | http://host2.com, 170 | https://host3.com`, false, []string{ 171 | "http://host1.com", 172 | "http://host2.com", 173 | "https://host3.com", 174 | }, map[string]int{}}, 175 | 176 | {`http://host1.com:1234, https://host2.com 177 | dir1 foo { 178 | bar baz 179 | } 180 | dir2`, false, []string{ 181 | "http://host1.com:1234", 182 | "https://host2.com", 183 | }, map[string]int{ 184 | "dir1": 6, 185 | "dir2": 1, 186 | }}, 187 | 188 | {`127.0.0.1 189 | dir1 { 190 | bar baz 191 | } 192 | dir2 { 193 | foo bar 194 | }`, false, []string{ 195 | "127.0.0.1", 196 | }, map[string]int{ 197 | "dir1": 5, 198 | "dir2": 5, 199 | }}, 200 | 201 | {`localhost 202 | dir1 { 203 | foo`, true, []string{ 204 | "localhost", 205 | }, map[string]int{ 206 | "dir1": 3, 207 | }}, 208 | 209 | {`localhost 210 | dir1 { 211 | }`, false, []string{ 212 | "localhost", 213 | }, map[string]int{ 214 | "dir1": 3, 215 | }}, 216 | 217 | {`localhost 218 | dir1 { 219 | } }`, true, []string{ 220 | "localhost", 221 | }, map[string]int{ 222 | "dir1": 3, 223 | }}, 224 | 225 | {`localhost 226 | dir1 { 227 | nested { 228 | foo 229 | } 230 | } 231 | dir2 foo bar`, false, []string{ 232 | "localhost", 233 | }, map[string]int{ 234 | "dir1": 7, 235 | "dir2": 3, 236 | }}, 237 | 238 | {``, false, []string{}, map[string]int{}}, 239 | 240 | {`localhost 241 | dir1 arg1 242 | import testdata/import_test1.txt`, false, []string{ 243 | "localhost", 244 | }, map[string]int{ 245 | "dir1": 2, 246 | "dir2": 3, 247 | "dir3": 1, 248 | }}, 249 | 250 | {`import testdata/import_test2.txt`, false, []string{ 251 | "host1", 252 | }, map[string]int{ 253 | "dir1": 1, 254 | "dir2": 2, 255 | }}, 256 | 257 | {`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, map[string]int{}}, 258 | 259 | {`import testdata/not_found.txt`, true, []string{}, map[string]int{}}, 260 | 261 | {`""`, false, []string{}, map[string]int{}}, 262 | 263 | {``, false, []string{}, map[string]int{}}, 264 | 265 | // test cases found by fuzzing! 266 | {`import }{$"`, true, []string{}, map[string]int{}}, 267 | {`import /*/*.txt`, true, []string{}, map[string]int{}}, 268 | {`import /???/?*?o`, true, []string{}, map[string]int{}}, 269 | {`import /??`, true, []string{}, map[string]int{}}, 270 | {`import /[a-z]`, true, []string{}, map[string]int{}}, 271 | {`import {$}`, true, []string{}, map[string]int{}}, 272 | {`import {%}`, true, []string{}, map[string]int{}}, 273 | {`import {$$}`, true, []string{}, map[string]int{}}, 274 | {`import {%%}`, true, []string{}, map[string]int{}}, 275 | } { 276 | result, err := testParseOne(test.input) 277 | 278 | if test.shouldErr && err == nil { 279 | t.Errorf("Test %d: Expected an error, but didn't get one", i) 280 | } 281 | if !test.shouldErr && err != nil { 282 | t.Errorf("Test %d: Expected no error, but got: %v", i, err) 283 | } 284 | 285 | if len(result.Keys) != len(test.keys) { 286 | t.Errorf("Test %d: Expected %d keys, got %d", 287 | i, len(test.keys), len(result.Keys)) 288 | continue 289 | } 290 | for j, addr := range result.Keys { 291 | if addr != test.keys[j] { 292 | t.Errorf("Test %d, key %d: Expected '%s', but was '%s'", 293 | i, j, test.keys[j], addr) 294 | } 295 | } 296 | 297 | if len(result.Tokens) != len(test.tokens) { 298 | t.Errorf("Test %d: Expected %d directives, had %d", 299 | i, len(test.tokens), len(result.Tokens)) 300 | continue 301 | } 302 | for directive, tokens := range result.Tokens { 303 | if len(tokens) != test.tokens[directive] { 304 | t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d", 305 | i, directive, test.tokens[directive], len(tokens)) 306 | continue 307 | } 308 | } 309 | } 310 | } 311 | 312 | func TestRecursiveImport(t *testing.T) { 313 | testParseOne := func(input string) (ServerBlock, error) { 314 | p := testParser(input) 315 | p.Next() // parseOne doesn't call Next() to start, so we must 316 | err := p.parseOne() 317 | return p.block, err 318 | } 319 | 320 | isExpected := func(got ServerBlock) bool { 321 | if len(got.Keys) != 1 || got.Keys[0] != "localhost" { 322 | t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys) 323 | return false 324 | } 325 | if len(got.Tokens) != 2 { 326 | t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens)) 327 | return false 328 | } 329 | if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["dir2"]) != 2 { 330 | t.Errorf("got unexpect tokens: %v", got.Tokens) 331 | return false 332 | } 333 | return true 334 | } 335 | 336 | recursiveFile1, err := filepath.Abs("testdata/recursive_import_test1") 337 | if err != nil { 338 | t.Fatal(err) 339 | } 340 | recursiveFile2, err := filepath.Abs("testdata/recursive_import_test2") 341 | if err != nil { 342 | t.Fatal(err) 343 | } 344 | 345 | // test relative recursive import 346 | err = ioutil.WriteFile(recursiveFile1, []byte( 347 | `localhost 348 | dir1 349 | import recursive_import_test2`), 0644) 350 | if err != nil { 351 | t.Fatal(err) 352 | } 353 | defer os.Remove(recursiveFile1) 354 | 355 | err = ioutil.WriteFile(recursiveFile2, []byte("dir2 1"), 0644) 356 | if err != nil { 357 | t.Fatal(err) 358 | } 359 | defer os.Remove(recursiveFile2) 360 | 361 | // import absolute path 362 | result, err := testParseOne("import " + recursiveFile1) 363 | if err != nil { 364 | t.Fatal(err) 365 | } 366 | if !isExpected(result) { 367 | t.Error("absolute+relative import failed") 368 | } 369 | 370 | // import relative path 371 | result, err = testParseOne("import testdata/recursive_import_test1") 372 | if err != nil { 373 | t.Fatal(err) 374 | } 375 | if !isExpected(result) { 376 | t.Error("relative+relative import failed") 377 | } 378 | 379 | // test absolute recursive import 380 | err = ioutil.WriteFile(recursiveFile1, []byte( 381 | `localhost 382 | dir1 383 | import `+recursiveFile2), 0644) 384 | if err != nil { 385 | t.Fatal(err) 386 | } 387 | 388 | // import absolute path 389 | result, err = testParseOne("import " + recursiveFile1) 390 | if err != nil { 391 | t.Fatal(err) 392 | } 393 | if !isExpected(result) { 394 | t.Error("absolute+absolute import failed") 395 | } 396 | 397 | // import relative path 398 | result, err = testParseOne("import testdata/recursive_import_test1") 399 | if err != nil { 400 | t.Fatal(err) 401 | } 402 | if !isExpected(result) { 403 | t.Error("relative+absolute import failed") 404 | } 405 | } 406 | 407 | func TestDirectiveImport(t *testing.T) { 408 | testParseOne := func(input string) (ServerBlock, error) { 409 | p := testParser(input) 410 | p.Next() // parseOne doesn't call Next() to start, so we must 411 | err := p.parseOne() 412 | return p.block, err 413 | } 414 | 415 | isExpected := func(got ServerBlock) bool { 416 | if len(got.Keys) != 1 || got.Keys[0] != "localhost" { 417 | t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys) 418 | return false 419 | } 420 | if len(got.Tokens) != 2 { 421 | t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens)) 422 | return false 423 | } 424 | if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 { 425 | t.Errorf("got unexpect tokens: %v", got.Tokens) 426 | return false 427 | } 428 | return true 429 | } 430 | 431 | directiveFile, err := filepath.Abs("testdata/directive_import_test") 432 | if err != nil { 433 | t.Fatal(err) 434 | } 435 | 436 | err = ioutil.WriteFile(directiveFile, []byte(`prop1 1 437 | prop2 2`), 0644) 438 | if err != nil { 439 | t.Fatal(err) 440 | } 441 | defer os.Remove(directiveFile) 442 | 443 | // import from existing file 444 | result, err := testParseOne(`localhost 445 | dir1 446 | proxy { 447 | import testdata/directive_import_test 448 | transparent 449 | }`) 450 | if err != nil { 451 | t.Fatal(err) 452 | } 453 | if !isExpected(result) { 454 | t.Error("directive import failed") 455 | } 456 | 457 | // import from nonexistent file 458 | _, err = testParseOne(`localhost 459 | dir1 460 | proxy { 461 | import testdata/nonexistent_file 462 | transparent 463 | }`) 464 | if err == nil { 465 | t.Fatal("expected error when importing a nonexistent file") 466 | } 467 | } 468 | 469 | func TestParseAll(t *testing.T) { 470 | for i, test := range []struct { 471 | input string 472 | shouldErr bool 473 | keys [][]string // keys per server block, in order 474 | }{ 475 | {`localhost`, false, [][]string{ 476 | {"localhost"}, 477 | }}, 478 | 479 | {`localhost:1234`, false, [][]string{ 480 | {"localhost:1234"}, 481 | }}, 482 | 483 | {`localhost:1234 { 484 | } 485 | localhost:2015 { 486 | }`, false, [][]string{ 487 | {"localhost:1234"}, 488 | {"localhost:2015"}, 489 | }}, 490 | 491 | {`localhost:1234, http://host2`, false, [][]string{ 492 | {"localhost:1234", "http://host2"}, 493 | }}, 494 | 495 | {`localhost:1234, http://host2,`, true, [][]string{}}, 496 | 497 | {`http://host1.com, http://host2.com { 498 | } 499 | https://host3.com, https://host4.com { 500 | }`, false, [][]string{ 501 | {"http://host1.com", "http://host2.com"}, 502 | {"https://host3.com", "https://host4.com"}, 503 | }}, 504 | 505 | {`import testdata/import_glob*.txt`, false, [][]string{ 506 | {"glob0.host0"}, 507 | {"glob0.host1"}, 508 | {"glob1.host0"}, 509 | {"glob2.host0"}, 510 | }}, 511 | 512 | {`import notfound/*`, false, [][]string{}}, // glob needn't error with no matches 513 | {`import notfound/file.conf`, true, [][]string{}}, // but a specific file should 514 | } { 515 | p := testParser(test.input) 516 | blocks, err := p.parseAll() 517 | 518 | if test.shouldErr && err == nil { 519 | t.Errorf("Test %d: Expected an error, but didn't get one", i) 520 | } 521 | if !test.shouldErr && err != nil { 522 | t.Errorf("Test %d: Expected no error, but got: %v", i, err) 523 | } 524 | 525 | if len(blocks) != len(test.keys) { 526 | t.Errorf("Test %d: Expected %d server blocks, got %d", 527 | i, len(test.keys), len(blocks)) 528 | continue 529 | } 530 | for j, block := range blocks { 531 | if len(block.Keys) != len(test.keys[j]) { 532 | t.Errorf("Test %d: Expected %d keys in block %d, got %d", 533 | i, len(test.keys[j]), j, len(block.Keys)) 534 | continue 535 | } 536 | for k, addr := range block.Keys { 537 | if addr != test.keys[j][k] { 538 | t.Errorf("Test %d, block %d, key %d: Expected '%s', but got '%s'", 539 | i, j, k, test.keys[j][k], addr) 540 | } 541 | } 542 | } 543 | } 544 | } 545 | 546 | func TestEnvironmentReplacement(t *testing.T) { 547 | os.Setenv("PORT", "8080") 548 | os.Setenv("ADDRESS", "servername.com") 549 | os.Setenv("FOOBAR", "foobar") 550 | os.Setenv("PARTIAL_DIR", "r1") 551 | 552 | // basic test; unix-style env vars 553 | p := testParser(`{$ADDRESS}`) 554 | blocks, _ := p.parseAll() 555 | if actual, expected := blocks[0].Keys[0], "servername.com"; expected != actual { 556 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 557 | } 558 | 559 | // basic test; unix-style env vars 560 | p = testParser(`di{$PARTIAL_DIR}`) 561 | blocks, _ = p.parseAll() 562 | if actual, expected := blocks[0].Keys[0], "dir1"; expected != actual { 563 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 564 | } 565 | 566 | // multiple vars per token 567 | p = testParser(`{$ADDRESS}:{$PORT}`) 568 | blocks, _ = p.parseAll() 569 | if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual { 570 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 571 | } 572 | 573 | // windows-style var and unix style in same token 574 | p = testParser(`{%ADDRESS%}:{$PORT}`) 575 | blocks, _ = p.parseAll() 576 | if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual { 577 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 578 | } 579 | 580 | // reverse order 581 | p = testParser(`{$ADDRESS}:{%PORT%}`) 582 | blocks, _ = p.parseAll() 583 | if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual { 584 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 585 | } 586 | 587 | // env var in server block body as argument 588 | p = testParser(":{%PORT%}\ndir1 {$FOOBAR}") 589 | blocks, _ = p.parseAll() 590 | if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual { 591 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 592 | } 593 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "foobar"; expected != actual { 594 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 595 | } 596 | 597 | // combined windows env vars in argument 598 | p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}") 599 | blocks, _ = p.parseAll() 600 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "servername.com/foobar"; expected != actual { 601 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 602 | } 603 | 604 | // malformed env var (windows) 605 | p = testParser(":1234\ndir1 {%ADDRESS}") 606 | blocks, _ = p.parseAll() 607 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "{%ADDRESS}"; expected != actual { 608 | t.Errorf("Expected host to be '%s' but was '%s'", expected, actual) 609 | } 610 | 611 | // malformed (non-existent) env var (unix) 612 | p = testParser(`:{$PORT$}`) 613 | blocks, _ = p.parseAll() 614 | if actual, expected := blocks[0].Keys[0], ":"; expected != actual { 615 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual) 616 | } 617 | 618 | // in quoted field 619 | p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"") 620 | blocks, _ = p.parseAll() 621 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "Test foobar test"; expected != actual { 622 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 623 | } 624 | 625 | // after end token 626 | p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"") 627 | blocks, _ = p.parseAll() 628 | if actual, expected := blocks[0].Tokens["answer"][1].Text, "{{ .Name }} foobar"; expected != actual { 629 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 630 | } 631 | } 632 | 633 | func testParser(input string) parser { 634 | buf := strings.NewReader(input) 635 | p := parser{Dispenser: NewDispenser("Caddyfile", buf)} 636 | return p 637 | } 638 | 639 | func TestSnippets(t *testing.T) { 640 | p := testParser(` 641 | (common) { 642 | gzip foo 643 | errors stderr 644 | } 645 | http://example.com { 646 | import common 647 | } 648 | `) 649 | blocks, err := p.parseAll() 650 | if err != nil { 651 | t.Fatal(err) 652 | } 653 | for _, b := range blocks { 654 | t.Log(b.Keys) 655 | t.Log(b.Tokens) 656 | } 657 | if len(blocks) != 1 { 658 | t.Fatalf("Expect exactly one server block. Got %d.", len(blocks)) 659 | } 660 | if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual { 661 | t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual) 662 | } 663 | if len(blocks[0].Tokens) != 2 { 664 | t.Fatalf("Server block should have tokens from import") 665 | } 666 | if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual { 667 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 668 | } 669 | if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual { 670 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 671 | } 672 | 673 | } 674 | 675 | func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) { 676 | file, err := ioutil.TempFile("", t.Name()) 677 | if err != nil { 678 | panic(err) // get a stack trace so we know where this was called from. 679 | } 680 | if _, err := file.WriteString(str); err != nil { 681 | panic(err) 682 | } 683 | if err := file.Close(); err != nil { 684 | panic(err) 685 | } 686 | return file.Name() 687 | } 688 | 689 | func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) { 690 | fileName := writeStringToTempFileOrDie(t, ` 691 | http://example.com { 692 | # This isn't an import directive, it's just an arg with value 'import' 693 | basicauth / import password 694 | } 695 | `) 696 | // Parse the root file that imports the other one. 697 | p := testParser(`import ` + fileName) 698 | blocks, err := p.parseAll() 699 | if err != nil { 700 | t.Fatal(err) 701 | } 702 | for _, b := range blocks { 703 | t.Log(b.Keys) 704 | t.Log(b.Tokens) 705 | } 706 | auth := blocks[0].Tokens["basicauth"] 707 | line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text 708 | if line != "basicauth / import password" { 709 | // Previously, it would be changed to: 710 | // basicauth / import /path/to/test/dir/password 711 | // referencing a file that (probably) doesn't exist and changing the 712 | // password! 713 | t.Errorf("Expected basicauth tokens to be 'basicauth / import password' but got %#q", line) 714 | } 715 | } 716 | 717 | func TestSnippetAcrossMultipleFiles(t *testing.T) { 718 | // Make the derived Caddyfile that expects (common) to be defined. 719 | fileName := writeStringToTempFileOrDie(t, ` 720 | http://example.com { 721 | import common 722 | } 723 | `) 724 | 725 | // Parse the root file that defines (common) and then imports the other one. 726 | p := testParser(` 727 | (common) { 728 | gzip foo 729 | } 730 | import ` + fileName + ` 731 | `) 732 | 733 | blocks, err := p.parseAll() 734 | if err != nil { 735 | t.Fatal(err) 736 | } 737 | for _, b := range blocks { 738 | t.Log(b.Keys) 739 | t.Log(b.Tokens) 740 | } 741 | if len(blocks) != 1 { 742 | t.Fatalf("Expect exactly one server block. Got %d.", len(blocks)) 743 | } 744 | if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual { 745 | t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual) 746 | } 747 | if len(blocks[0].Tokens) != 1 { 748 | t.Fatalf("Server block should have tokens from import") 749 | } 750 | if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual { 751 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual) 752 | } 753 | } 754 | 755 | func TestSnippetImportCycle(t *testing.T) { 756 | tests := []struct { 757 | name string 758 | input string 759 | }{ 760 | { 761 | name: "direct self-import", 762 | input: ` 763 | (loop) { 764 | import loop 765 | } 766 | import loop 767 | `, 768 | }, 769 | { 770 | name: "self-import via recursion", 771 | input: ` 772 | (loop) { 773 | { } 774 | import loop 775 | } 776 | import loop 777 | `, 778 | }, 779 | } 780 | 781 | for _, tt := range tests { 782 | t.Run(tt.name, func(t *testing.T) { 783 | p := testParser(tt.input) 784 | _, err := p.parseAll() 785 | if err == nil { 786 | t.Fatalf("expected error for snippet self-import cycle, got nil") 787 | } 788 | }) 789 | } 790 | } 791 | 792 | func TestFileImportCycleError(t *testing.T) { 793 | fileA := writeStringToTempFileOrDie(t, "") 794 | defer os.Remove(fileA) 795 | fileB := writeStringToTempFileOrDie(t, "") 796 | defer os.Remove(fileB) 797 | 798 | if err := ioutil.WriteFile(fileA, []byte("import "+fileB), 0644); err != nil { 799 | t.Fatal(err) 800 | } 801 | if err := ioutil.WriteFile(fileB, []byte("import "+fileA), 0644); err != nil { 802 | t.Fatal(err) 803 | } 804 | 805 | p := testParser("import " + fileA) 806 | _, err := p.parseAll() 807 | if err == nil { 808 | t.Fatalf("expected error for file import cycle, got nil") 809 | } 810 | } 811 | 812 | func TestFileImportDir(t *testing.T) { 813 | dir, err := ioutil.TempDir("", t.Name()) 814 | if err != nil { 815 | t.Fatal(err) 816 | } 817 | defer os.RemoveAll(dir) 818 | 819 | // create 10x the maxFileExpansions files 820 | // a single import with a glob should not error 821 | for i := 0; i < maxFileExpansions*10; i++ { 822 | fp := filepath.Join(dir, filepath.Base(dir)+"_"+fmt.Sprintf("%d", i)) 823 | if err := ioutil.WriteFile(fp, []byte(""), 0644); err != nil { 824 | t.Fatal(err) 825 | } 826 | } 827 | 828 | input := "import " + filepath.Join(dir, "*") 829 | p := testParser(input) 830 | _, err = p.parseAll() 831 | if err != nil { 832 | t.Fatalf("unexpected error importing temp dir via glob: %v", err) 833 | } 834 | } 835 | -------------------------------------------------------------------------------- /caddy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Light Code Labs, LLC 2 | // 3 | // Copyright 2024 MWS 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | // Package caddy implements the Caddy server manager. 18 | // 19 | // To use this package: 20 | // 21 | // 1. Set the AppName and AppVersion variables. 22 | // 2. Call LoadCaddyfile() to get the Caddyfile. 23 | // Pass in the name of the server type (like "http"). 24 | // Make sure the server type's package is imported 25 | // (import _ "github.com/coredns/caddy/caddyhttp"). 26 | // 3. Call caddy.Start() to start Caddy. You get back 27 | // an Instance, on which you can call Restart() to 28 | // restart it or Stop() to stop it. 29 | // 30 | // You should call Wait() on your instance to wait for 31 | // all servers to quit before your process exits. 32 | package caddy 33 | 34 | import ( 35 | "bytes" 36 | "encoding/gob" 37 | "fmt" 38 | "io" 39 | "io/ioutil" 40 | "log" 41 | "net" 42 | "os" 43 | "strconv" 44 | "strings" 45 | "sync" 46 | "time" 47 | 48 | "github.com/coredns/caddy/caddyfile" 49 | ) 50 | 51 | // Configurable application parameters 52 | var ( 53 | // AppName is the name of the application. 54 | AppName string 55 | 56 | // AppVersion is the version of the application. 57 | AppVersion string 58 | 59 | // Quiet mode will not show any informative output on initialization. 60 | Quiet bool 61 | 62 | // PidFile is the path to the pidfile to create. 63 | PidFile string 64 | 65 | // GracefulTimeout is the maximum duration of a graceful shutdown. 66 | GracefulTimeout time.Duration 67 | 68 | // isUpgrade will be set to true if this process 69 | // was started as part of an upgrade, where a parent 70 | // Caddy process started this one. 71 | isUpgrade = os.Getenv("CADDY__UPGRADE") == "1" 72 | 73 | // started will be set to true when the first 74 | // instance is started; it never gets set to 75 | // false after that. 76 | started bool 77 | 78 | // mu protects the variables 'isUpgrade' and 'started'. 79 | mu sync.Mutex 80 | ) 81 | 82 | func init() { 83 | OnProcessExit = append(OnProcessExit, func() { 84 | if PidFile != "" { 85 | os.Remove(PidFile) 86 | } 87 | }) 88 | } 89 | 90 | // Instance contains the state of servers created as a result of 91 | // calling Start and can be used to access or control those servers. 92 | // It is literally an instance of a server type. Instance values 93 | // should NOT be copied. Use *Instance for safety. 94 | type Instance struct { 95 | // serverType is the name of the instance's server type 96 | serverType string 97 | 98 | // caddyfileInput is the input configuration text used for this process 99 | caddyfileInput Input 100 | 101 | // wg is used to wait for all servers to shut down 102 | wg *sync.WaitGroup 103 | 104 | // context is the context created for this instance, 105 | // used to coordinate the setting up of the server type 106 | context Context 107 | 108 | // servers is the list of servers with their listeners 109 | servers []ServerListener 110 | 111 | // these callbacks execute when certain events occur 112 | OnFirstStartup []func() error // starting, not as part of a restart 113 | OnStartup []func() error // starting, even as part of a restart 114 | OnRestart []func() error // before restart commences 115 | OnRestartFailed []func() error // if restart failed 116 | OnShutdown []func() error // stopping, even as part of a restart 117 | OnFinalShutdown []func() error // stopping, not as part of a restart 118 | 119 | // storing values on an instance is preferable to 120 | // global state because these will get garbage- 121 | // collected after in-process reloads when the 122 | // old instances are destroyed; use StorageMu 123 | // to access this value safely 124 | Storage map[interface{}]interface{} 125 | StorageMu sync.RWMutex 126 | } 127 | 128 | // Instances returns the list of instances. 129 | func Instances() []*Instance { 130 | return instances 131 | } 132 | 133 | // Servers returns the ServerListeners in i. 134 | func (i *Instance) Servers() []ServerListener { return i.servers } 135 | 136 | // Stop stops all servers contained in i. It does NOT 137 | // execute shutdown callbacks. 138 | func (i *Instance) Stop() error { 139 | // stop the servers 140 | for _, s := range i.servers { 141 | if gs, ok := s.server.(GracefulServer); ok { 142 | if err := gs.Stop(); err != nil { 143 | log.Printf("[ERROR] Stopping %s: %v", gs.Address(), err) 144 | } 145 | } 146 | } 147 | 148 | // splice i out of instance list, causing it to be garbage-collected 149 | instancesMu.Lock() 150 | for j, other := range instances { 151 | if other == i { 152 | instances = append(instances[:j], instances[j+1:]...) 153 | break 154 | } 155 | } 156 | instancesMu.Unlock() 157 | 158 | return nil 159 | } 160 | 161 | // ShutdownCallbacks executes all the shutdown callbacks of i, 162 | // including ones that are scheduled only for the final shutdown 163 | // of i. An error returned from one does not stop execution of 164 | // the rest. All the non-nil errors will be returned. 165 | func (i *Instance) ShutdownCallbacks() []error { 166 | var errs []error 167 | for _, shutdownFunc := range i.OnShutdown { 168 | err := shutdownFunc() 169 | if err != nil { 170 | errs = append(errs, err) 171 | } 172 | } 173 | for _, finalShutdownFunc := range i.OnFinalShutdown { 174 | err := finalShutdownFunc() 175 | if err != nil { 176 | errs = append(errs, err) 177 | } 178 | } 179 | return errs 180 | } 181 | 182 | // Restart replaces the servers in i with new servers created from 183 | // executing the newCaddyfile. Upon success, it returns the new 184 | // instance to replace i. Upon failure, i will not be replaced. 185 | func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) { 186 | log.Println("[INFO] Reloading") 187 | 188 | i.wg.Add(1) 189 | defer i.wg.Done() 190 | 191 | var err error 192 | // if something went wrong on restart then run onRestartFailed callbacks 193 | defer func() { 194 | r := recover() 195 | if err != nil || r != nil { 196 | for _, fn := range i.OnRestartFailed { 197 | if err := fn(); err != nil { 198 | log.Printf("[ERROR] Restart failed callback returned error: %v", err) 199 | } 200 | } 201 | if err != nil { 202 | log.Printf("[ERROR] Restart failed: %v", err) 203 | } 204 | if r != nil { 205 | log.Printf("[PANIC] Restart: %v", r) 206 | } 207 | } 208 | }() 209 | 210 | // run restart callbacks 211 | for _, fn := range i.OnRestart { 212 | err = fn() 213 | if err != nil { 214 | return i, err 215 | } 216 | } 217 | 218 | if newCaddyfile == nil { 219 | newCaddyfile = i.caddyfileInput 220 | } 221 | 222 | // Add file descriptors of all the sockets that are capable of it 223 | restartFds := make(map[string][]restartTriple) 224 | for _, s := range i.servers { 225 | gs, srvOk := s.server.(GracefulServer) 226 | ln, lnOk := s.listener.(Listener) 227 | pc, pcOk := s.packet.(PacketConn) 228 | if srvOk { 229 | if lnOk && pcOk { 230 | restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, listener: ln, packet: pc}) 231 | continue 232 | } 233 | if lnOk { 234 | restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, listener: ln}) 235 | continue 236 | } 237 | if pcOk { 238 | restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, packet: pc}) 239 | continue 240 | } 241 | } 242 | } 243 | 244 | // create new instance; if the restart fails, it is simply discarded 245 | newInst := &Instance{serverType: newCaddyfile.ServerType(), wg: i.wg, Storage: make(map[interface{}]interface{})} 246 | 247 | // attempt to start new instance 248 | err = startWithListenerFds(newCaddyfile, newInst, restartFds) 249 | if err != nil { 250 | return i, fmt.Errorf("starting with listener file descriptors: %v", err) 251 | } 252 | 253 | // success! stop the old instance 254 | err = i.Stop() 255 | if err != nil { 256 | return i, err 257 | } 258 | for _, shutdownFunc := range i.OnShutdown { 259 | err = shutdownFunc() 260 | if err != nil { 261 | return i, err 262 | } 263 | } 264 | 265 | // Execute instantiation events 266 | EmitEvent(InstanceStartupEvent, newInst) 267 | 268 | log.Println("[INFO] Reloading complete") 269 | 270 | return newInst, nil 271 | } 272 | 273 | // SaveServer adds s and its associated listener ln to the 274 | // internally-kept list of servers that is running. For 275 | // saved servers, graceful restarts will be provided. 276 | func (i *Instance) SaveServer(s Server, ln net.Listener) { 277 | i.servers = append(i.servers, ServerListener{server: s, listener: ln}) 278 | } 279 | 280 | // TCPServer is a type that can listen and serve connections. 281 | // A TCPServer must associate with exactly zero or one net.Listeners. 282 | type TCPServer interface { 283 | // Listen starts listening by creating a new listener 284 | // and returning it. It does not start accepting 285 | // connections. For UDP-only servers, this method 286 | // can be a no-op that returns (nil, nil). 287 | Listen() (net.Listener, error) 288 | 289 | // Serve starts serving using the provided listener. 290 | // Serve must start the server loop nearly immediately, 291 | // or at least not return any errors before the server 292 | // loop begins. Serve blocks indefinitely, or in other 293 | // words, until the server is stopped. For UDP-only 294 | // servers, this method can be a no-op that returns nil. 295 | Serve(net.Listener) error 296 | } 297 | 298 | // UDPServer is a type that can listen and serve packets. 299 | // A UDPServer must associate with exactly zero or one net.PacketConns. 300 | type UDPServer interface { 301 | // ListenPacket starts listening by creating a new packetconn 302 | // and returning it. It does not start accepting connections. 303 | // TCP-only servers may leave this method blank and return 304 | // (nil, nil). 305 | ListenPacket() (net.PacketConn, error) 306 | 307 | // ServePacket starts serving using the provided packetconn. 308 | // ServePacket must start the server loop nearly immediately, 309 | // or at least not return any errors before the server 310 | // loop begins. ServePacket blocks indefinitely, or in other 311 | // words, until the server is stopped. For TCP-only servers, 312 | // this method can be a no-op that returns nil. 313 | ServePacket(net.PacketConn) error 314 | } 315 | 316 | // Server is a type that can listen and serve. It supports both 317 | // TCP and UDP, although the UDPServer interface can be used 318 | // for more than just UDP. 319 | // 320 | // If the server uses TCP, it should implement TCPServer completely. 321 | // If it uses UDP or some other protocol, it should implement 322 | // UDPServer completely. If it uses both, both interfaces should be 323 | // fully implemented. Any unimplemented methods should be made as 324 | // no-ops that simply return nil values. 325 | type Server interface { 326 | TCPServer 327 | UDPServer 328 | } 329 | 330 | // Stopper is a type that can stop serving. The stop 331 | // does not necessarily have to be graceful. 332 | type Stopper interface { 333 | // Stop stops the server. It blocks until the 334 | // server is completely stopped. 335 | Stop() error 336 | } 337 | 338 | // GracefulServer is a Server and Stopper, the stopping 339 | // of which is graceful (whatever that means for the kind 340 | // of server being implemented). It must be able to return 341 | // the address it is configured to listen on so that its 342 | // listener can be paired with it upon graceful restarts. 343 | // The net.Listener that a GracefulServer creates must 344 | // implement the Listener interface for restarts to be 345 | // graceful (assuming the listener is for TCP). 346 | type GracefulServer interface { 347 | Server 348 | Stopper 349 | 350 | // Address returns the address the server should 351 | // listen on; it is used to pair the server to 352 | // its listener during a graceful/zero-downtime 353 | // restart. Thus when implementing this method, 354 | // you must not access a listener to get the 355 | // address; you must store the address the 356 | // server is to serve on some other way. 357 | Address() string 358 | 359 | // WrapListener wraps a listener with the 360 | // listener middlewares configured for this 361 | // server, if any. 362 | WrapListener(net.Listener) net.Listener 363 | } 364 | 365 | // Listener is a net.Listener with an underlying file descriptor. 366 | // A server's listener should implement this interface if it is 367 | // to support zero-downtime reloads. 368 | type Listener interface { 369 | net.Listener 370 | File() (*os.File, error) 371 | } 372 | 373 | // PacketConn is a net.PacketConn with an underlying file descriptor. 374 | // A server's packetconn should implement this interface if it is 375 | // to support zero-downtime reloads (in sofar this holds true for datagram 376 | // connections). 377 | type PacketConn interface { 378 | net.PacketConn 379 | File() (*os.File, error) 380 | } 381 | 382 | // AfterStartup is an interface that can be implemented 383 | // by a server type that wants to run some code after all 384 | // servers for the same Instance have started. 385 | type AfterStartup interface { 386 | OnStartupComplete() 387 | } 388 | 389 | // LoadCaddyfile loads a Caddyfile by calling the plugged in 390 | // Caddyfile loader methods. An error is returned if more than 391 | // one loader returns a non-nil Caddyfile input. If no loaders 392 | // load a Caddyfile, the default loader is used. If no default 393 | // loader is registered or it returns nil, the server type's 394 | // default Caddyfile is loaded. If the server type does not 395 | // specify any default Caddyfile value, then an empty Caddyfile 396 | // is returned. Consequently, this function never returns a nil 397 | // value as long as there are no errors. 398 | func LoadCaddyfile(serverType string) (Input, error) { 399 | // If we are finishing an upgrade, we must obtain the Caddyfile 400 | // from our parent process, regardless of configured loaders. 401 | if IsUpgrade() { 402 | err := gob.NewDecoder(os.Stdin).Decode(&loadedGob) 403 | if err != nil { 404 | return nil, err 405 | } 406 | return loadedGob.Caddyfile, nil 407 | } 408 | 409 | // Ask plugged-in loaders for a Caddyfile 410 | cdyfile, err := loadCaddyfileInput(serverType) 411 | if err != nil { 412 | return nil, err 413 | } 414 | 415 | // Otherwise revert to default 416 | if cdyfile == nil { 417 | cdyfile = DefaultInput(serverType) 418 | } 419 | 420 | // Still nil? Geez. 421 | if cdyfile == nil { 422 | cdyfile = CaddyfileInput{ServerTypeName: serverType} 423 | } 424 | 425 | return cdyfile, nil 426 | } 427 | 428 | // Wait blocks until all of i's servers have stopped. 429 | func (i *Instance) Wait() { 430 | i.wg.Wait() 431 | } 432 | 433 | // CaddyfileFromPipe loads the Caddyfile input from f if f is 434 | // not interactive input. f is assumed to be a pipe or stream, 435 | // such as os.Stdin. If f is not a pipe, no error is returned 436 | // but the Input value will be nil. An error is only returned 437 | // if there was an error reading the pipe, even if the length 438 | // of what was read is 0. 439 | func CaddyfileFromPipe(f *os.File, serverType string) (Input, error) { 440 | fi, err := f.Stat() 441 | if err == nil && fi.Mode()&os.ModeCharDevice == 0 { 442 | // Note that a non-nil error is not a problem. Windows 443 | // will not create a stdin if there is no pipe, which 444 | // produces an error when calling Stat(). But Unix will 445 | // make one either way, which is why we also check that 446 | // bitmask. 447 | // NOTE: Reading from stdin after this fails (e.g. for the let's encrypt email address) (OS X) 448 | confBody, err := ioutil.ReadAll(f) 449 | if err != nil { 450 | return nil, err 451 | } 452 | return CaddyfileInput{ 453 | Contents: confBody, 454 | Filepath: f.Name(), 455 | ServerTypeName: serverType, 456 | }, nil 457 | } 458 | 459 | // not having input from the pipe is not itself an error, 460 | // just means no input to return. 461 | return nil, nil 462 | } 463 | 464 | // Caddyfile returns the Caddyfile used to create i. 465 | func (i *Instance) Caddyfile() Input { 466 | return i.caddyfileInput 467 | } 468 | 469 | // Start starts Caddy with the given Caddyfile. 470 | // 471 | // This function blocks until all the servers are listening. 472 | func Start(cdyfile Input) (*Instance, error) { 473 | inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})} 474 | err := startWithListenerFds(cdyfile, inst, nil) 475 | if err != nil { 476 | return inst, err 477 | } 478 | signalSuccessToParent() 479 | if pidErr := writePidFile(); pidErr != nil { 480 | log.Printf("[ERROR] Could not write pidfile: %v", pidErr) 481 | } 482 | 483 | // Execute instantiation events 484 | EmitEvent(InstanceStartupEvent, inst) 485 | 486 | return inst, nil 487 | } 488 | 489 | func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string][]restartTriple) error { 490 | // save this instance in the list now so that 491 | // plugins can access it if need be, for example 492 | // the caddytls package, so it can perform cert 493 | // renewals while starting up; we just have to 494 | // remove the instance from the list later if 495 | // it fails 496 | instancesMu.Lock() 497 | instances = append(instances, inst) 498 | instancesMu.Unlock() 499 | var err error 500 | defer func() { 501 | if err != nil { 502 | instancesMu.Lock() 503 | for i, otherInst := range instances { 504 | if otherInst == inst { 505 | instances = append(instances[:i], instances[i+1:]...) 506 | break 507 | } 508 | } 509 | instancesMu.Unlock() 510 | } 511 | }() 512 | 513 | if cdyfile == nil { 514 | cdyfile = CaddyfileInput{} 515 | } 516 | 517 | err = ValidateAndExecuteDirectives(cdyfile, inst, false) 518 | if err != nil { 519 | return err 520 | } 521 | 522 | slist, err := inst.context.MakeServers() 523 | if err != nil { 524 | return err 525 | } 526 | 527 | // run startup callbacks 528 | if !IsUpgrade() && restartFds == nil { 529 | // first startup means not a restart or upgrade 530 | for _, firstStartupFunc := range inst.OnFirstStartup { 531 | err = firstStartupFunc() 532 | if err != nil { 533 | return err 534 | } 535 | } 536 | } 537 | for _, startupFunc := range inst.OnStartup { 538 | err = startupFunc() 539 | if err != nil { 540 | return err 541 | } 542 | } 543 | 544 | err = startServers(slist, inst, restartFds) 545 | if err != nil { 546 | return err 547 | } 548 | 549 | // run any AfterStartup callbacks if this is not 550 | // part of a restart; then show file descriptor notice 551 | if restartFds == nil { 552 | for _, srvln := range inst.servers { 553 | if srv, ok := srvln.server.(AfterStartup); ok { 554 | srv.OnStartupComplete() 555 | } 556 | } 557 | if !Quiet { 558 | for _, srvln := range inst.servers { 559 | // only show FD notice if the listener is not nil. 560 | // This can happen when only serving UDP or TCP 561 | if srvln.listener == nil { 562 | continue 563 | } 564 | if !IsLoopback(srvln.listener.Addr().String()) { 565 | checkFdlimit() 566 | break 567 | } 568 | } 569 | } 570 | } 571 | 572 | mu.Lock() 573 | started = true 574 | mu.Unlock() 575 | 576 | return nil 577 | } 578 | 579 | // ValidateAndExecuteDirectives will load the server blocks from cdyfile 580 | // by parsing it, then execute the directives configured by it and store 581 | // the resulting server blocks into inst. If justValidate is true, parse 582 | // callbacks will not be executed between directives, since the purpose 583 | // is only to check the input for valid syntax. 584 | func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bool) error { 585 | // If parsing only inst will be nil, create an instance for this function call only. 586 | if justValidate { 587 | inst = &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})} 588 | } 589 | 590 | stypeName := cdyfile.ServerType() 591 | 592 | stype, err := getServerType(stypeName) 593 | if err != nil { 594 | return err 595 | } 596 | 597 | inst.caddyfileInput = cdyfile 598 | 599 | sblocks, err := loadServerBlocks(stypeName, cdyfile.Path(), bytes.NewReader(cdyfile.Body())) 600 | if err != nil { 601 | return err 602 | } 603 | 604 | inst.context = stype.NewContext(inst) 605 | if inst.context == nil { 606 | return fmt.Errorf("server type %s produced a nil Context", stypeName) 607 | } 608 | 609 | sblocks, err = inst.context.InspectServerBlocks(cdyfile.Path(), sblocks) 610 | if err != nil { 611 | return fmt.Errorf("error inspecting server blocks: %v", err) 612 | } 613 | 614 | return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate) 615 | } 616 | 617 | func executeDirectives(inst *Instance, filename string, 618 | directives []string, sblocks []caddyfile.ServerBlock, justValidate bool) error { 619 | // map of server block ID to map of directive name to whatever. 620 | storages := make(map[int]map[string]interface{}) 621 | 622 | // It is crucial that directives are executed in the proper order. 623 | // We loop with the directives on the outer loop so we execute 624 | // a directive for all server blocks before going to the next directive. 625 | // This is important mainly due to the parsing callbacks (below). 626 | for _, dir := range directives { 627 | for i, sb := range sblocks { 628 | var once sync.Once 629 | if _, ok := storages[i]; !ok { 630 | storages[i] = make(map[string]interface{}) 631 | } 632 | 633 | for j, key := range sb.Keys { 634 | // Execute directive if it is in the server block 635 | if tokens, ok := sb.Tokens[dir]; ok { 636 | controller := &Controller{ 637 | instance: inst, 638 | Key: key, 639 | Dispenser: caddyfile.NewDispenserTokens(filename, tokens), 640 | OncePerServerBlock: func(f func() error) error { 641 | var err error 642 | once.Do(func() { 643 | err = f() 644 | }) 645 | return err 646 | }, 647 | ServerBlockIndex: i, 648 | ServerBlockKeyIndex: j, 649 | ServerBlockKeys: sb.Keys, 650 | ServerBlockStorage: storages[i][dir], 651 | } 652 | 653 | // only set up directives for the first key in a block 654 | if j > 0 { 655 | continue 656 | } 657 | 658 | setup, err := DirectiveAction(inst.serverType, dir) 659 | if err != nil { 660 | return err 661 | } 662 | 663 | err = setup(controller) 664 | if err != nil { 665 | return err 666 | } 667 | 668 | storages[i][dir] = controller.ServerBlockStorage // persist for this server block 669 | } 670 | } 671 | } 672 | 673 | if !justValidate { 674 | // See if there are any callbacks to execute after this directive 675 | if allCallbacks, ok := parsingCallbacks[inst.serverType]; ok { 676 | callbacks := allCallbacks[dir] 677 | for _, callback := range callbacks { 678 | if err := callback(inst.context); err != nil { 679 | return err 680 | } 681 | } 682 | } 683 | } 684 | } 685 | 686 | return nil 687 | } 688 | 689 | func startServers(serverList []Server, inst *Instance, restartFds map[string][]restartTriple) error { 690 | errChan := make(chan error, len(serverList)) 691 | 692 | // used for signaling to error logging goroutine to terminate 693 | stopChan := make(chan struct{}) 694 | // used to track termination of servers 695 | stopWg := &sync.WaitGroup{} 696 | 697 | for _, s := range serverList { 698 | var ( 699 | ln net.Listener 700 | pc net.PacketConn 701 | err error 702 | ) 703 | 704 | // if performing an upgrade, obtain listener file descriptors 705 | // from parent process 706 | if IsUpgrade() { 707 | if gs, ok := s.(GracefulServer); ok { 708 | addr := gs.Address() 709 | if fdIndex, ok := loadedGob.ListenerFds["tcp"+addr]; ok { 710 | file := os.NewFile(fdIndex, "") 711 | ln, err = net.FileListener(file) 712 | if err != nil { 713 | return fmt.Errorf("making listener from file: %v", err) 714 | } 715 | err = file.Close() 716 | if err != nil { 717 | return fmt.Errorf("closing copy of listener file: %v", err) 718 | } 719 | } 720 | if fdIndex, ok := loadedGob.ListenerFds["udp"+addr]; ok { 721 | file := os.NewFile(fdIndex, "") 722 | pc, err = net.FilePacketConn(file) 723 | if err != nil { 724 | return fmt.Errorf("making packet connection from file: %v", err) 725 | } 726 | err = file.Close() 727 | if err != nil { 728 | return fmt.Errorf("closing copy of packet connection file: %v", err) 729 | } 730 | } 731 | ln = gs.WrapListener(ln) 732 | } 733 | } 734 | 735 | // If this is a reload and s is a GracefulServer, 736 | // reuse the listener for a graceful restart. 737 | if gs, ok := s.(GracefulServer); ok && restartFds != nil { 738 | addr := gs.Address() 739 | // Multiple servers may use the same addr (SO_REUSEPORT option set), so it's important to ensure 740 | // that we don't reuse the same listener/packetconn. 741 | // We'll create new listeners in case there are no more available triples for the same address. 742 | if triples, ok := restartFds[addr]; ok && len(triples) > 0 { 743 | // Take first available triple 744 | old := triples[0] 745 | // Remove reused triple from restartFds 746 | triples[0] = triples[len(triples)-1] 747 | restartFds[addr] = triples[:len(triples)-1] 748 | 749 | // listener 750 | if old.listener != nil { 751 | file, err := old.listener.File() 752 | if err != nil { 753 | return fmt.Errorf("getting old listener file: %v", err) 754 | } 755 | ln, err = net.FileListener(file) 756 | if err != nil { 757 | return fmt.Errorf("getting file listener: %v", err) 758 | } 759 | err = file.Close() 760 | if err != nil { 761 | return fmt.Errorf("closing copy of listener file: %v", err) 762 | } 763 | } 764 | // packetconn 765 | if old.packet != nil { 766 | file, err := old.packet.File() 767 | if err != nil { 768 | return fmt.Errorf("getting old packet file: %v", err) 769 | } 770 | pc, err = net.FilePacketConn(file) 771 | if err != nil { 772 | return fmt.Errorf("getting file packet connection: %v", err) 773 | } 774 | err = file.Close() 775 | if err != nil { 776 | return fmt.Errorf("close copy of packet file: %v", err) 777 | } 778 | } 779 | ln = gs.WrapListener(ln) 780 | } 781 | } 782 | 783 | if ln == nil { 784 | ln, err = s.Listen() 785 | if err != nil { 786 | return fmt.Errorf("Listen: %v", err) 787 | } 788 | } 789 | if pc == nil { 790 | pc, err = s.ListenPacket() 791 | if err != nil { 792 | return fmt.Errorf("ListenPacket: %v", err) 793 | } 794 | } 795 | 796 | inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc}) 797 | } 798 | 799 | for _, s := range inst.servers { 800 | inst.wg.Add(2) 801 | stopWg.Add(2) 802 | func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) { 803 | go func() { 804 | defer func() { 805 | inst.wg.Done() 806 | stopWg.Done() 807 | }() 808 | errChan <- s.Serve(ln) 809 | }() 810 | 811 | go func() { 812 | defer func() { 813 | inst.wg.Done() 814 | stopWg.Done() 815 | }() 816 | errChan <- s.ServePacket(pc) 817 | }() 818 | }(s.server, s.listener, s.packet, inst) 819 | } 820 | 821 | // Log errors that may be returned from Serve() calls, 822 | // these errors should only be occurring in the server loop. 823 | go func() { 824 | for { 825 | select { 826 | case err := <-errChan: 827 | if err != nil { 828 | if !strings.Contains(err.Error(), "use of closed network connection") { 829 | // this error is normal when closing the listener; see https://github.com/golang/go/issues/4373 830 | log.Println(err) 831 | } 832 | } 833 | case <-stopChan: 834 | return 835 | } 836 | } 837 | }() 838 | 839 | go func() { 840 | stopWg.Wait() 841 | stopChan <- struct{}{} 842 | }() 843 | 844 | return nil 845 | } 846 | 847 | func getServerType(serverType string) (ServerType, error) { 848 | stype, ok := serverTypes[serverType] 849 | if ok { 850 | return stype, nil 851 | } 852 | if len(serverTypes) == 0 { 853 | return ServerType{}, fmt.Errorf("no server types plugged in") 854 | } 855 | if serverType == "" { 856 | if len(serverTypes) == 1 { 857 | for _, stype := range serverTypes { 858 | return stype, nil 859 | } 860 | } 861 | return ServerType{}, fmt.Errorf("multiple server types available; must choose one") 862 | } 863 | return ServerType{}, fmt.Errorf("unknown server type '%s'", serverType) 864 | } 865 | 866 | func loadServerBlocks(serverType, filename string, input io.Reader) ([]caddyfile.ServerBlock, error) { 867 | validDirectives := ValidDirectives(serverType) 868 | serverBlocks, err := caddyfile.Parse(filename, input, validDirectives) 869 | if err != nil { 870 | return nil, err 871 | } 872 | if len(serverBlocks) == 0 && serverTypes[serverType].DefaultInput != nil { 873 | newInput := serverTypes[serverType].DefaultInput() 874 | serverBlocks, err = caddyfile.Parse(newInput.Path(), 875 | bytes.NewReader(newInput.Body()), validDirectives) 876 | if err != nil { 877 | return nil, err 878 | } 879 | } 880 | return serverBlocks, nil 881 | } 882 | 883 | // Stop stops ALL servers. It blocks until they are all stopped. 884 | // It does NOT execute shutdown callbacks, and it deletes all 885 | // instances after stopping is completed. Do not re-use any 886 | // references to old instances after calling Stop. 887 | func Stop() error { 888 | // This awkward for loop is to avoid a deadlock since 889 | // inst.Stop() also acquires the instancesMu lock. 890 | for { 891 | instancesMu.Lock() 892 | if len(instances) == 0 { 893 | instancesMu.Unlock() 894 | break 895 | } 896 | inst := instances[0] 897 | instancesMu.Unlock() 898 | // Increase the instance waitgroup so that the last wait() call in 899 | // caddymain/run.go blocks until this server instance has shut down 900 | inst.wg.Add(1) 901 | defer inst.wg.Done() 902 | if err := inst.Stop(); err != nil { 903 | log.Printf("[ERROR] Stopping %s: %v", inst.serverType, err) 904 | } 905 | } 906 | return nil 907 | } 908 | 909 | // IsLoopback returns true if the hostname of addr looks 910 | // explicitly like a common local hostname. addr must only 911 | // be a host or a host:port combination. 912 | func IsLoopback(addr string) bool { 913 | host, _, err := net.SplitHostPort(strings.ToLower(addr)) 914 | if err != nil { 915 | host = addr // happens if the addr is just a hostname 916 | } 917 | return host == "localhost" || 918 | strings.Trim(host, "[]") == "::1" || 919 | strings.HasPrefix(host, "127.") 920 | } 921 | 922 | // IsInternal returns true if the IP of addr 923 | // belongs to a private network IP range. addr must only 924 | // be an IP or an IP:port combination. 925 | // Loopback addresses are considered false. 926 | func IsInternal(addr string) bool { 927 | privateNetworks := []string{ 928 | "10.0.0.0/8", 929 | "172.16.0.0/12", 930 | "192.168.0.0/16", 931 | "fc00::/7", 932 | } 933 | 934 | host, _, err := net.SplitHostPort(addr) 935 | if err != nil { 936 | host = addr // happens if the addr is just a hostname, missing port 937 | // if we encounter an error, the brackets need to be stripped 938 | // because SplitHostPort didn't do it for us 939 | host = strings.Trim(host, "[]") 940 | } 941 | ip := net.ParseIP(host) 942 | if ip == nil { 943 | return false 944 | } 945 | for _, privateNetwork := range privateNetworks { 946 | _, ipnet, _ := net.ParseCIDR(privateNetwork) 947 | if ipnet.Contains(ip) { 948 | return true 949 | } 950 | } 951 | return false 952 | } 953 | 954 | // Started returns true if at least one instance has been 955 | // started by this package. It never gets reset to false 956 | // once it is set to true. 957 | func Started() bool { 958 | mu.Lock() 959 | defer mu.Unlock() 960 | return started 961 | } 962 | 963 | // CaddyfileInput represents a Caddyfile as input 964 | // and is simply a convenient way to implement 965 | // the Input interface. 966 | type CaddyfileInput struct { 967 | Filepath string 968 | Contents []byte 969 | ServerTypeName string 970 | } 971 | 972 | // Body returns c.Contents. 973 | func (c CaddyfileInput) Body() []byte { return c.Contents } 974 | 975 | // Path returns c.Filepath. 976 | func (c CaddyfileInput) Path() string { return c.Filepath } 977 | 978 | // ServerType returns c.ServerType. 979 | func (c CaddyfileInput) ServerType() string { return c.ServerTypeName } 980 | 981 | // Input represents a Caddyfile; its contents and file path 982 | // (which should include the file name at the end of the path). 983 | // If path does not apply (e.g. piped input) you may use 984 | // any understandable value. The path is mainly used for logging, 985 | // error messages, and debugging. 986 | type Input interface { 987 | // Gets the Caddyfile contents 988 | Body() []byte 989 | 990 | // Gets the path to the origin file 991 | Path() string 992 | 993 | // The type of server this input is intended for 994 | ServerType() string 995 | } 996 | 997 | // DefaultInput returns the default Caddyfile input 998 | // to use when it is otherwise empty or missing. 999 | // It uses the default host and port (depends on 1000 | // host, e.g. localhost is 2015, otherwise 443) and 1001 | // root. 1002 | func DefaultInput(serverType string) Input { 1003 | if _, ok := serverTypes[serverType]; !ok { 1004 | return nil 1005 | } 1006 | if serverTypes[serverType].DefaultInput == nil { 1007 | return nil 1008 | } 1009 | return serverTypes[serverType].DefaultInput() 1010 | } 1011 | 1012 | // writePidFile writes the process ID to the file at PidFile. 1013 | // It does nothing if PidFile is not set. 1014 | func writePidFile() error { 1015 | if PidFile == "" { 1016 | return nil 1017 | } 1018 | pid := []byte(strconv.Itoa(os.Getpid()) + "\n") 1019 | return ioutil.WriteFile(PidFile, pid, 0644) 1020 | } 1021 | 1022 | type restartTriple struct { 1023 | server GracefulServer 1024 | listener Listener 1025 | packet PacketConn 1026 | } 1027 | 1028 | var ( 1029 | // instances is the list of running Instances. 1030 | instances []*Instance 1031 | 1032 | // instancesMu protects instances. 1033 | instancesMu sync.Mutex 1034 | ) 1035 | 1036 | var ( 1037 | // DefaultConfigFile is the name of the configuration file that is loaded 1038 | // by default if no other file is specified. 1039 | DefaultConfigFile = "Caddyfile" 1040 | ) 1041 | 1042 | // CtxKey is a value type for use with context.WithValue. 1043 | type CtxKey string 1044 | --------------------------------------------------------------------------------