Commit 9ebc11d7 authored by Toby Allen's avatar Toby Allen Committed by GitHub

Merge branch 'master' into macros

parents 4ec5522a 689591ef
......@@ -57,7 +57,12 @@ Caddy binaries have no dependencies and are available for every platform. Get Ca
customize your build in the browser
- **[Latest release](https://github.com/mholt/caddy/releases/latest)** for
pre-built, vanilla binaries
- **go get** to build from source: `go get github.com/mholt/caddy/caddy` (requires Go 1.8 or newer) - to build with proper version information (required when filing issues), `cd` to the `caddy` folder and use `go get github.com/caddyserver/builds` followed by `go run build.go`.
## Build
To build from source you need **[Git](https://git-scm.com/downloads)** and **[Go](https://golang.org/doc/install)** (1.8 or newer). Follow these instruction for fast building:
- Get source `go get github.com/mholt/caddy/caddy` and then run `go get github.com/caddyserver/builds`
- Now `cd` to `$GOPATH/src/github.com/mholt/caddy/caddy` and run `go run build.go`
Then make sure the `caddy` binary is in your PATH.
......
......@@ -25,9 +25,15 @@ func TestAssetsPath(t *testing.T) {
t.Errorf("Expected path to be a .caddy folder, got: %v", actual)
}
os.Setenv("CADDYPATH", "testpath")
err := os.Setenv("CADDYPATH", "testpath")
if err != nil {
t.Error("Could not set CADDYPATH")
}
if actual, expected := AssetsPath(), "testpath"; actual != expected {
t.Errorf("Expected path to be %v, got: %v", expected, actual)
}
os.Setenv("CADDYPATH", "")
err = os.Setenv("CADDYPATH", "")
if err != nil {
t.Error("Could not set CADDYPATH")
}
}
......@@ -206,12 +206,15 @@ func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
// success! stop the old instance
for _, shutdownFunc := range i.onShutdown {
err := shutdownFunc()
err = shutdownFunc()
if err != nil {
return i, err
}
}
i.Stop()
err = i.Stop()
if err != nil {
return i, err
}
// Execute instantiation events
EmitEvent(InstanceStartupEvent, newInst)
......@@ -483,14 +486,14 @@ func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]r
if !IsUpgrade() && restartFds == nil {
// first startup means not a restart or upgrade
for _, firstStartupFunc := range inst.onFirstStartup {
err := firstStartupFunc()
err = firstStartupFunc()
if err != nil {
return err
}
}
}
for _, startupFunc := range inst.onStartup {
err := startupFunc()
err = startupFunc()
if err != nil {
return err
}
......@@ -565,12 +568,7 @@ func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bo
return err
}
err = executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
if err != nil {
return err
}
return nil
return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
}
func executeDirectives(inst *Instance, filename string,
......@@ -658,7 +656,10 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
if fdIndex, ok := loadedGob.ListenerFds["tcp"+addr]; ok {
file := os.NewFile(fdIndex, "")
ln, err = net.FileListener(file)
file.Close()
if err != nil {
return err
}
err = file.Close()
if err != nil {
return err
}
......@@ -666,7 +667,10 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
if fdIndex, ok := loadedGob.ListenerFds["udp"+addr]; ok {
file := os.NewFile(fdIndex, "")
pc, err = net.FilePacketConn(file)
file.Close()
if err != nil {
return err
}
err = file.Close()
if err != nil {
return err
}
......@@ -689,7 +693,10 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
if err != nil {
return err
}
file.Close()
err = file.Close()
if err != nil {
return err
}
}
// packetconn
if old.packet != nil {
......@@ -701,7 +708,10 @@ func startServers(serverList []Server, inst *Instance, restartFds map[string]res
if err != nil {
return err
}
file.Close()
err = file.Close()
if err != nil {
return err
}
}
}
}
......
......@@ -252,7 +252,7 @@ func directoryListing(files []os.FileInfo, canGoUp bool, urlPath string, config
for _, f := range files {
name := f.Name()
for _, indexName := range staticfiles.IndexPages {
for _, indexName := range config.Fs.IndexPages {
if name == indexName {
hasIndexFile = true
break
......
......@@ -78,8 +78,9 @@ func browseParse(c *caddy.Controller) ([]Config, error) {
}
bc.Fs = staticfiles.FileServer{
Root: http.Dir(cfg.Root),
Hide: cfg.HiddenFiles,
Root: http.Dir(cfg.Root),
Hide: cfg.HiddenFiles,
IndexPages: cfg.IndexPages,
}
// Second argument would be the template file to use
......
......@@ -27,6 +27,7 @@ import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyfile"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/mholt/caddy/caddytls"
)
......@@ -155,6 +156,7 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
AltTLSSNIPort: altTLSSNIPort,
},
originCaddyfile: sourceFile,
IndexPages: staticfiles.DefaultIndexPages,
}
h.saveConfig(key, cfg)
}
......@@ -234,7 +236,7 @@ func GetConfig(c *caddy.Controller) *SiteConfig {
// we should only get here during tests because directive
// actions typically skip the server blocks where we make
// the configs
cfg := &SiteConfig{Root: Root, TLS: new(caddytls.Config)}
cfg := &SiteConfig{Root: Root, TLS: new(caddytls.Config), IndexPages: staticfiles.DefaultIndexPages}
ctx.saveConfig(key, cfg)
return cfg
}
......
......@@ -142,7 +142,7 @@ func NewServer(addr string, group []*SiteConfig) (*Server, error) {
// Compile custom middleware for every site (enables virtual hosting)
for _, site := range group {
stack := Handler(staticfiles.FileServer{Root: http.Dir(site.Root), Hide: site.HiddenFiles})
stack := Handler(staticfiles.FileServer{Root: http.Dir(site.Root), Hide: site.HiddenFiles, IndexPages: site.IndexPages})
for i := len(site.middleware) - 1; i >= 0; i-- {
stack = site.middleware[i](stack)
}
......
......@@ -26,6 +26,9 @@ type SiteConfig struct {
// The address of the site
Addr Address
// The list of viable index page names of the site
IndexPages []string
// The hostname to bind listener to;
// defaults to Addr.Host
ListenHost string
......
......@@ -16,7 +16,7 @@ package index
import (
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/staticfiles"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func init() {
......@@ -29,6 +29,8 @@ func init() {
func setupIndex(c *caddy.Controller) error {
var index []string
cfg := httpserver.GetConfig(c)
for c.Next() {
args := c.RemainingArgs()
......@@ -40,7 +42,7 @@ func setupIndex(c *caddy.Controller) error {
index = append(index, in)
}
staticfiles.IndexPages = index
cfg.IndexPages = index
}
return nil
......
......@@ -18,6 +18,7 @@ import (
"testing"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
......@@ -31,7 +32,7 @@ func TestIndexIncompleteParams(t *testing.T) {
}
func TestIndex(t *testing.T) {
c := caddy.NewTestController("", "index a.html b.html c.html")
c := caddy.NewTestController("http", "index a.html b.html c.html")
err := setupIndex(c)
if err != nil {
......@@ -40,14 +41,85 @@ func TestIndex(t *testing.T) {
expectedIndex := []string{"a.html", "b.html", "c.html"}
if len(staticfiles.IndexPages) != 3 {
t.Errorf("Expected 3 values, got %v", len(staticfiles.IndexPages))
siteConfig := httpserver.GetConfig(c)
if len(siteConfig.IndexPages) != len(expectedIndex) {
t.Errorf("Expected 3 values, got %v", len(siteConfig.IndexPages))
}
// Ensure ordering is correct
for i, actual := range staticfiles.IndexPages {
for i, actual := range siteConfig.IndexPages {
if actual != expectedIndex[i] {
t.Errorf("Expected value in position %d to be %v, got %v", i, expectedIndex[i], actual)
}
}
}
func TestMultiSiteIndexWithEitherHasDefault(t *testing.T) {
// TestIndex already covers the correctness of the directive
// when used on a single controller, so no need to verify test setupIndex again.
// This sets the stage for the actual verification.
customIndex := caddy.NewTestController("http", "index a.html b.html")
// setupIndex against customIdx should not pollute the
// index list for other controllers.
err := setupIndex(customIndex)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
// Represents a virtual host with no index directive.
defaultIndex := caddy.NewTestController("http", "")
// Not calling setupIndex because it guards against lack of arguments,
// and we need to ensure the site gets the default set of index pages.
siteConfig := httpserver.GetConfig(defaultIndex)
// In case the index directive is not used, the virtual host
// should receive staticfiles.DefaultIndexPages slice. The length, as checked here,
// and the values, as checked in the upcoming loop, should match.
if len(siteConfig.IndexPages) != len(staticfiles.DefaultIndexPages) {
t.Errorf("Expected %d values, got %d", len(staticfiles.DefaultIndexPages), len(siteConfig.IndexPages))
}
// Ensure values match the expected default index pages
for i, actual := range siteConfig.IndexPages {
if actual != staticfiles.DefaultIndexPages[i] {
t.Errorf("Expected value in position %d to be %v, got %v", i, staticfiles.DefaultIndexPages[i], actual)
}
}
}
func TestPerSiteIndexPageIsolation(t *testing.T) {
firstIndex := "first.html"
secondIndex := "second.html"
// Create two sites with different index page configurations
firstSite := caddy.NewTestController("http", "index first.html")
err := setupIndex(firstSite)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
secondSite := caddy.NewTestController("http", "index second.html")
err = setupIndex(secondSite)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
firstSiteConfig := httpserver.GetConfig(firstSite)
if firstSiteConfig.IndexPages[0] != firstIndex {
t.Errorf("Expected index for first site as %s, received %s", firstIndex, firstSiteConfig.IndexPages[0])
}
secondSiteConfig := httpserver.GetConfig(secondSite)
if secondSiteConfig.IndexPages[0] != secondIndex {
t.Errorf("Expected index for second site as %s, received %s", secondIndex, secondSiteConfig.IndexPages[0])
}
// They should have different index pages, as per the provided config.
if firstSiteConfig.IndexPages[0] == secondSiteConfig.IndexPages[0] {
t.Errorf("Expected different index pages for both sites, got %s for first and %s for second", firstSiteConfig.IndexPages[0], secondSiteConfig.IndexPages[0])
}
}
......@@ -19,7 +19,6 @@ import (
"strings"
"github.com/mholt/caddy/caddyhttp/httpserver"
"github.com/mholt/caddy/caddyhttp/staticfiles"
)
func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
......@@ -44,7 +43,7 @@ outer:
matches := httpserver.Path(urlPath).Matches(rule.Path)
// Also check IndexPages when requesting a directory
if !matches {
indexFile, isIndexFile := httpserver.IndexFile(h.Root, urlPath, staticfiles.IndexPages)
indexFile, isIndexFile := httpserver.IndexFile(h.Root, urlPath, h.indexPages)
if isIndexFile {
matches = httpserver.Path(indexFile).Matches(rule.Path)
}
......
......@@ -393,7 +393,8 @@ func TestMiddlewareShouldPushIndexFile(t *testing.T) {
{Path: "/index.css", Method: http.MethodGet},
}},
},
Root: http.Dir(root),
Root: http.Dir(root),
indexPages: []string{indexFile},
}
indexFilePath := filepath.Join(root, indexFile)
......
......@@ -36,9 +36,10 @@ type (
// Middleware supports pushing resources to clients
Middleware struct {
Next httpserver.Handler
Rules []Rule
Root http.FileSystem
Next httpserver.Handler
Rules []Rule
Root http.FileSystem
indexPages []string // will be injected from SiteConfig on setup
}
ruleOp func([]Resource)
......
......@@ -50,7 +50,7 @@ func setup(c *caddy.Controller) error {
cfg := httpserver.GetConfig(c)
cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return Middleware{Next: next, Rules: rules, Root: http.Dir(cfg.Root)}
return Middleware{Next: next, Rules: rules, Root: http.Dir(cfg.Root), indexPages: cfg.IndexPages}
})
return nil
......
......@@ -45,6 +45,10 @@ import (
type FileServer struct {
Root http.FileSystem // jailed access to the file system
Hide []string // list of files for which to respond with "Not Found"
// A list of pages that may be understood as the "index" files to directories.
// Injected from *SiteConfig.
IndexPages []string
}
// ServeHTTP serves static files for r according to fs's configuration.
......@@ -118,7 +122,7 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
// if an index file was explicitly requested, strip file name from the request
// ("/foo/index.html" -> "/foo/")
var requestPage = path.Base(urlCopy.Path)
for _, indexPage := range IndexPages {
for _, indexPage := range fs.IndexPages {
if requestPage == indexPage {
urlCopy.Path = urlCopy.Path[:len(urlCopy.Path)-len(indexPage)]
redir = true
......@@ -134,7 +138,7 @@ func (fs FileServer) serveFile(w http.ResponseWriter, r *http.Request) (int, err
// use contents of an index file, if present, for directory requests
if d.IsDir() {
for _, indexPage := range IndexPages {
for _, indexPage := range fs.IndexPages {
indexPath := path.Join(reqPath, indexPage)
indexFile, err := fs.Root.Open(indexPath)
if err != nil {
......@@ -253,9 +257,9 @@ func calculateEtag(d os.FileInfo) string {
return `"` + t + s + `"`
}
// IndexPages is a list of pages that may be understood as
// DefaultIndexPages is a list of pages that may be understood as
// the "index" files to directories.
var IndexPages = []string{
var DefaultIndexPages = []string{
"index.html",
"index.htm",
"index.txt",
......
......@@ -36,8 +36,9 @@ func TestServeHTTP(t *testing.T) {
defer afterServeHTTPTest(t, tmpWebRootDir)
fileserver := FileServer{
Root: http.Dir(filepath.Join(tmpWebRootDir, webrootName)),
Hide: []string{"dir/hidden.html"},
Root: http.Dir(filepath.Join(tmpWebRootDir, webrootName)),
Hide: []string{"dir/hidden.html"},
IndexPages: DefaultIndexPages,
}
movedPermanently := "Moved Permanently"
......
......@@ -148,6 +148,11 @@ type OnDemandState struct {
// Set from max_certs in tls config, it specifies the
// maximum number of certificates that can be issued.
MaxObtain int32
// The url to call to check if an on-demand tls certificate should
// be issued. If a request to the URL fails or returns a non 2xx
// status on-demand issuances must fail.
AskURL *url.URL
}
// ObtainCert obtains a certificate for name using c, as long
......
......@@ -19,6 +19,8 @@ import (
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"sync"
"sync/atomic"
......@@ -135,8 +137,8 @@ func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIf
name = strings.ToLower(name)
// Make sure aren't over any applicable limits
err := cfg.checkLimitsForObtainingNewCerts(name)
// Make sure the certificate should be obtained based on config
err := cfg.checkIfCertShouldBeObtained(name)
if err != nil {
return Certificate{}, err
}
......@@ -159,10 +161,52 @@ func (cfg *Config) getCertDuringHandshake(name string, loadIfNecessary, obtainIf
return Certificate{}, fmt.Errorf("no certificate available for %s", name)
}
// checkIfCertShouldBeObtained checks to see if an on-demand tls certificate
// should be obtained for a given domain based upon the config settings. If
// a non-nil error is returned, do not issue a new certificate for name.
func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
// If the "ask" URL is defined in the config, use to determine if a
// cert should obtained
if cfg.OnDemandState.AskURL != nil {
return cfg.checkURLForObtainingNewCerts(name)
}
// Otherwise use the limit defined by the "max_certs" setting
return cfg.checkLimitsForObtainingNewCerts(name)
}
func (cfg *Config) checkURLForObtainingNewCerts(name string) error {
client := http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return errors.New("following http redirects is not allowed")
},
}
// Copy the URL from the config in order to modify it for this request
askURL := new(url.URL)
*askURL = *cfg.OnDemandState.AskURL
query := askURL.Query()
query.Set("domain", name)
askURL.RawQuery = query.Encode()
resp, err := client.Get(askURL.String())
if err != nil {
return fmt.Errorf("error checking %v to deterine if certificate for hostname '%s' should be allowed: %v", cfg.OnDemandState.AskURL, name, err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return fmt.Errorf("certificate for hostname '%s' not allowed, non-2xx status code %d returned from %v", name, resp.StatusCode, cfg.OnDemandState.AskURL)
}
return nil
}
// checkLimitsForObtainingNewCerts checks to see if name can be issued right
// now according to mitigating factors we keep track of and preferences the
// user has set. If a non-nil error is returned, do not issue a new certificate
// for name.
// now according the maximum count defined in the configuration. If a non-nil
// error is returned, do not issue a new certificate for name.
func (cfg *Config) checkLimitsForObtainingNewCerts(name string) error {
// User can set hard limit for number of certs for the process to issue
if cfg.OnDemandState.MaxObtain > 0 &&
......
......@@ -21,6 +21,7 @@ import (
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"path/filepath"
"strconv"
......@@ -49,7 +50,7 @@ func setupTLS(c *caddy.Controller) error {
config.Enabled = true
for c.Next() {
var certificateFile, keyFile, loadDir, maxCerts string
var certificateFile, keyFile, loadDir, maxCerts, askURL string
args := c.RemainingArgs()
switch len(args) {
......@@ -164,6 +165,9 @@ func setupTLS(c *caddy.Controller) error {
case "max_certs":
c.Args(&maxCerts)
config.OnDemand = true
case "ask":
c.Args(&askURL)
config.OnDemand = true
case "dns":
args := c.RemainingArgs()
if len(args) != 1 {
......@@ -213,6 +217,19 @@ func setupTLS(c *caddy.Controller) error {
config.OnDemandState.MaxObtain = int32(maxCertsNum)
}
if askURL != "" {
parsedURL, err := url.Parse(askURL)
if err != nil {
return c.Err("ask must be a valid url")
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return c.Err("ask URL must use http or https")
}
config.OnDemandState.AskURL = parsedURL
}
// don't try to load certificates unless we're supposed to
if !config.Enabled || !config.Manual {
continue
......
......@@ -62,7 +62,7 @@ fi
pidfile="/var/run/${name}.pid"
procname="${caddy_bin_path}" #enabled builtin pid checking for start / stop
command="/usr/sbin/daemon"
command_args="-u ${caddy_user} -p ${pidfile} /usr/bin/env ${caddy_env} ${procname} -cpu ${caddy_cpu} -log stdout -conf ${caddy_config_path} -agree -email ${caddy_cert_email} < /dev/null >> ${caddy_logfile} 2>&1"
command_args="-p ${pidfile} /usr/bin/env ${caddy_env} ${procname} -cpu ${caddy_cpu} -log stdout -conf ${caddy_config_path} -agree -email ${caddy_cert_email} < /dev/null >> ${caddy_logfile} 2>&1"
start_precmd="caddy_startprecmd"
......
......@@ -28,10 +28,10 @@ func (cfg *Config) Hook(event caddy.EventName, info interface{}) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if nonblock {
log.Printf("[INFO] Nonblocking Command with ID %s: \"%s %s\"", cfg.ID, cfg.Command, strings.Join(cfg.Args, " "))
log.Printf("[INFO] Nonblocking Command \"%s %s\" with ID %s", cfg.Command, strings.Join(cfg.Args, " "), cfg.ID)
return cmd.Start()
}
log.Printf("[INFO] Blocking Command with ID %s: \"%s %s\"", cfg.ID, cfg.Command, strings.Join(cfg.Args, " "))
log.Printf("[INFO] Blocking Command \"%s %s\" with ID %s", cfg.Command, strings.Join(cfg.Args, " "), cfg.ID)
err := cmd.Run()
if err != nil {
return err
......
......@@ -20,9 +20,12 @@ func setup(c *caddy.Controller) error {
}
// Register Event Hooks.
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
c.OncePerServerBlock(func() error {
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
return nil
})
return nil
}
......
......@@ -84,7 +84,7 @@ func trapSignalsPosix() {
}
// Kick off the restart; our work is done
inst, err = inst.Restart(caddyfileToUse)
_, err = inst.Restart(caddyfileToUse)
if err != nil {
log.Printf("[ERROR] SIGUSR1: %v", err)
}
......
......@@ -36,9 +36,12 @@ func Startup(c *caddy.Controller) error {
}
// Register Event Hooks.
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
c.OncePerServerBlock(func() error {
for _, cfg := range config {
caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
}
return nil
})
fmt.Println("NOTICE: Startup directive will be removed in a later version. Please migrate to 'on startup'")
......
......@@ -136,7 +136,10 @@ func Upgrade() error {
// immediately close our dup'ed fds and the write end of our signal pipe
for _, f := range extraFiles {
f.Close()
err = f.Close()
if err != nil {
return err
}
}
// feed Caddyfile to the child
......@@ -144,11 +147,14 @@ func Upgrade() error {
if err != nil {
return err
}
wpipe.Close()
err = wpipe.Close()
if err != nil {
return err
}
// determine whether child startup succeeded
answer, readErr := ioutil.ReadAll(sigrpipe)
if answer == nil || len(answer) == 0 {
if len(answer) == 0 {
cmdErr := cmd.Wait() // get exit status
errStr := fmt.Sprintf("child failed to initialize: %v", cmdErr)
if readErr != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment