github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/server/camlistored/camlistored.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // The camlistored binary is the Camlistore server.
    18  package main
    19  
    20  import (
    21  	"crypto/rand"
    22  	"crypto/rsa"
    23  	"crypto/x509"
    24  	"crypto/x509/pkix"
    25  	"encoding/pem"
    26  	"flag"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"log"
    31  	"math/big"
    32  	"net"
    33  	"os"
    34  	"os/signal"
    35  	"path/filepath"
    36  	"runtime"
    37  	"strconv"
    38  	"strings"
    39  	"syscall"
    40  	"time"
    41  
    42  	"camlistore.org/pkg/buildinfo"
    43  	"camlistore.org/pkg/misc"
    44  	"camlistore.org/pkg/osutil"
    45  	"camlistore.org/pkg/serverinit"
    46  	"camlistore.org/pkg/webserver"
    47  
    48  	// Storage options:
    49  	_ "camlistore.org/pkg/blobserver/cond"
    50  	_ "camlistore.org/pkg/blobserver/diskpacked"
    51  	_ "camlistore.org/pkg/blobserver/encrypt"
    52  	_ "camlistore.org/pkg/blobserver/google/cloudstorage"
    53  	_ "camlistore.org/pkg/blobserver/google/drive"
    54  	_ "camlistore.org/pkg/blobserver/localdisk"
    55  	_ "camlistore.org/pkg/blobserver/mongo"
    56  	_ "camlistore.org/pkg/blobserver/proxycache"
    57  	_ "camlistore.org/pkg/blobserver/remote"
    58  	_ "camlistore.org/pkg/blobserver/replica"
    59  	_ "camlistore.org/pkg/blobserver/s3"
    60  	_ "camlistore.org/pkg/blobserver/shard"
    61  	// Indexers: (also present themselves as storage targets)
    62  	"camlistore.org/pkg/index"
    63  	// KeyValue implementations:
    64  	_ "camlistore.org/pkg/sorted/kvfile"
    65  	_ "camlistore.org/pkg/sorted/mongo"
    66  	_ "camlistore.org/pkg/sorted/mysql"
    67  	_ "camlistore.org/pkg/sorted/postgres"
    68  	"camlistore.org/pkg/sorted/sqlite" // for sqlite.CompiledIn()
    69  
    70  	// Handlers:
    71  	_ "camlistore.org/pkg/search"
    72  	_ "camlistore.org/pkg/server" // UI, publish, etc
    73  
    74  	// Importers:
    75  	_ "camlistore.org/pkg/importer/dummy"
    76  	_ "camlistore.org/pkg/importer/feed"
    77  	//_ "camlistore.org/pkg/importer/flickr"
    78  	_ "camlistore.org/pkg/importer/foursquare"
    79  	//_ "camlistore.org/pkg/importer/picasa"
    80  	_ "camlistore.org/pkg/importer/twitter"
    81  )
    82  
    83  var (
    84  	flagVersion    = flag.Bool("version", false, "show version")
    85  	flagConfigFile = flag.String("configfile", "",
    86  		"Config file to use, relative to the Camlistore configuration directory root. If blank, the default is used or auto-generated.")
    87  	listenFlag      = flag.String("listen", "", "host:port to listen on, or :0 to auto-select. If blank, the value in the config will be used instead.")
    88  	flagOpenBrowser = flag.Bool("openbrowser", true, "Launches the UI on startup")
    89  	flagReindex     = flag.Bool("reindex", false, "Reindex all blobs on startup")
    90  	flagPollParent  bool
    91  )
    92  
    93  func init() {
    94  	if debug, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG")); debug {
    95  		flag.BoolVar(&flagPollParent, "pollparent", false, "Camlistored regularly polls its parent process to detect if it has been orphaned, and terminates in that case. Mainly useful for tests.")
    96  	}
    97  }
    98  
    99  func exitf(pattern string, args ...interface{}) {
   100  	if !strings.HasSuffix(pattern, "\n") {
   101  		pattern = pattern + "\n"
   102  	}
   103  	fmt.Fprintf(os.Stderr, pattern, args...)
   104  	osExit(1)
   105  }
   106  
   107  // 1) We do not want to force the user to buy a cert.
   108  // 2) We still want our client (camput) to be able to
   109  // verify the cert's authenticity.
   110  // 3) We want to avoid MITM attacks and warnings in
   111  // the browser.
   112  // Using a simple self-signed won't do because of 3),
   113  // as Chrome offers no way to set a self-signed as
   114  // trusted when importing it. (same on android).
   115  // We could have created a self-signed CA (that we
   116  // would import in the browsers) and create another
   117  // cert (signed by that CA) which would be the one
   118  // used in camlistore.
   119  // We're doing even simpler: create a self-signed
   120  // CA and directly use it as a self-signed cert
   121  // (and install it as a CA in the browsers).
   122  // 2) is satisfied by doing our own checks,
   123  // See pkg/client
   124  func genSelfTLS(listen string) error {
   125  	priv, err := rsa.GenerateKey(rand.Reader, 1024)
   126  	if err != nil {
   127  		return fmt.Errorf("failed to generate private key: %s", err)
   128  	}
   129  
   130  	now := time.Now()
   131  
   132  	hostname, _, err := net.SplitHostPort(listen)
   133  	if err != nil {
   134  		return fmt.Errorf("splitting listen failed: %q", err)
   135  	}
   136  
   137  	// TODO(mpl): if no host is specified in the listening address
   138  	// (e.g ":3179") we'll end up in this case, and the self-signed
   139  	// will have "localhost" as a CommonName. But I don't think
   140  	// there's anything we can do about it. Maybe warn...
   141  	if hostname == "" {
   142  		hostname = "localhost"
   143  	}
   144  	template := x509.Certificate{
   145  		SerialNumber: new(big.Int).SetInt64(0),
   146  		Subject: pkix.Name{
   147  			CommonName:   hostname,
   148  			Organization: []string{hostname},
   149  		},
   150  		NotBefore:    now.Add(-5 * time.Minute).UTC(),
   151  		NotAfter:     now.AddDate(1, 0, 0).UTC(),
   152  		SubjectKeyId: []byte{1, 2, 3, 4},
   153  		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   154  		IsCA:         true,
   155  		BasicConstraintsValid: true,
   156  	}
   157  
   158  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
   159  	if err != nil {
   160  		return fmt.Errorf("Failed to create certificate: %s", err)
   161  	}
   162  
   163  	defCert := osutil.DefaultTLSCert()
   164  	defKey := osutil.DefaultTLSKey()
   165  	certOut, err := os.Create(defCert)
   166  	if err != nil {
   167  		return fmt.Errorf("failed to open %s for writing: %s", defCert, err)
   168  	}
   169  	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
   170  	certOut.Close()
   171  	log.Printf("written %s\n", defCert)
   172  	cert, err := x509.ParseCertificate(derBytes)
   173  	if err != nil {
   174  		return fmt.Errorf("Failed to parse certificate: %v", err)
   175  	}
   176  	sig := misc.SHA256Prefix(cert.Raw)
   177  	hint := "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n" +
   178  		`"trustedCerts": ["` + sig + `"],`
   179  	log.Printf(hint)
   180  
   181  	keyOut, err := os.OpenFile(defKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   182  	if err != nil {
   183  		return fmt.Errorf("failed to open %s for writing:", defKey, err)
   184  	}
   185  	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
   186  	keyOut.Close()
   187  	log.Printf("written %s\n", defKey)
   188  	return nil
   189  }
   190  
   191  // findConfigFile returns the absolute path of the user's
   192  // config file.
   193  // The provided file may be absolute or relative
   194  // to the user's configuration directory.
   195  // If file is empty, a default high-level config is written
   196  // for the user.
   197  func findConfigFile(file string) (absPath string, isNewConfig bool, err error) {
   198  	switch {
   199  	case file == "":
   200  		absPath = osutil.UserServerConfigPath()
   201  		_, err = os.Stat(absPath)
   202  		if os.IsNotExist(err) {
   203  			err = os.MkdirAll(osutil.CamliConfigDir(), 0700)
   204  			if err != nil {
   205  				return
   206  			}
   207  			log.Printf("Generating template config file %s", absPath)
   208  			if err = serverinit.WriteDefaultConfigFile(absPath, sqlite.CompiledIn()); err == nil {
   209  				isNewConfig = true
   210  			}
   211  		}
   212  		return
   213  	case filepath.IsAbs(file):
   214  		absPath = file
   215  	default:
   216  		absPath = filepath.Join(osutil.CamliConfigDir(), file)
   217  	}
   218  	_, err = os.Stat(absPath)
   219  	return
   220  }
   221  
   222  func setupTLS(ws *webserver.Server, config *serverinit.Config, listen string) {
   223  	cert, key := config.OptionalString("httpsCert", ""), config.OptionalString("httpsKey", "")
   224  	if !config.OptionalBool("https", true) {
   225  		return
   226  	}
   227  	if (cert != "") != (key != "") {
   228  		exitf("httpsCert and httpsKey must both be either present or absent")
   229  	}
   230  
   231  	defCert := osutil.DefaultTLSCert()
   232  	defKey := osutil.DefaultTLSKey()
   233  	if cert == defCert && key == defKey {
   234  		_, err1 := os.Stat(cert)
   235  		_, err2 := os.Stat(key)
   236  		if err1 != nil || err2 != nil {
   237  			if os.IsNotExist(err1) || os.IsNotExist(err2) {
   238  				if err := genSelfTLS(listen); err != nil {
   239  					exitf("Could not generate self-signed TLS cert: %q", err)
   240  				}
   241  			} else {
   242  				exitf("Could not stat cert or key: %q, %q", err1, err2)
   243  			}
   244  		}
   245  	}
   246  	if cert == "" && key == "" {
   247  		err := genSelfTLS(listen)
   248  		if err != nil {
   249  			exitf("Could not generate self signed creds: %q", err)
   250  		}
   251  		cert = defCert
   252  		key = defKey
   253  	}
   254  	data, err := ioutil.ReadFile(cert)
   255  	if err != nil {
   256  		exitf("Failed to read pem certificate: %s", err)
   257  	}
   258  	block, _ := pem.Decode(data)
   259  	if block == nil {
   260  		exitf("Failed to decode pem certificate")
   261  	}
   262  	certif, err := x509.ParseCertificate(block.Bytes)
   263  	if err != nil {
   264  		exitf("Failed to parse certificate: %v", err)
   265  	}
   266  	sig := misc.SHA256Prefix(certif.Raw)
   267  	log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig)
   268  	ws.SetTLS(cert, key)
   269  }
   270  
   271  var osExit = os.Exit // testing hook
   272  
   273  func handleSignals(shutdownc <-chan io.Closer) {
   274  	c := make(chan os.Signal, 1)
   275  	signal.Notify(c, syscall.SIGHUP)
   276  	signal.Notify(c, syscall.SIGINT)
   277  	for {
   278  		sig := <-c
   279  		sysSig, ok := sig.(syscall.Signal)
   280  		if !ok {
   281  			log.Fatal("Not a unix signal")
   282  		}
   283  		switch sysSig {
   284  		case syscall.SIGHUP:
   285  			log.Print("SIGHUP: restarting camli")
   286  			err := osutil.RestartProcess()
   287  			if err != nil {
   288  				log.Fatal("Failed to restart: " + err.Error())
   289  			}
   290  		case syscall.SIGINT:
   291  			log.Print("Got SIGINT: shutting down")
   292  			donec := make(chan bool)
   293  			go func() {
   294  				cl := <-shutdownc
   295  				if err := cl.Close(); err != nil {
   296  					exitf("Error shutting down: %v", err)
   297  				}
   298  				donec <- true
   299  			}()
   300  			select {
   301  			case <-donec:
   302  				log.Printf("Shut down.")
   303  				osExit(0)
   304  			case <-time.After(2 * time.Second):
   305  				exitf("Timeout shutting down. Exiting uncleanly.")
   306  			}
   307  		default:
   308  			log.Fatal("Received another signal, should not happen.")
   309  		}
   310  	}
   311  }
   312  
   313  // listenAndBaseURL finds the configured, default, or inferred listen address
   314  // and base URL from the command-line flags and provided config.
   315  func listenAndBaseURL(config *serverinit.Config) (listen, baseURL string) {
   316  	baseURL = config.OptionalString("baseURL", "")
   317  	listen = *listenFlag
   318  	listenConfig := config.OptionalString("listen", "")
   319  	// command-line takes priority over config
   320  	if listen == "" {
   321  		listen = listenConfig
   322  		if listen == "" {
   323  			exitf("\"listen\" needs to be specified either in the config or on the command line")
   324  		}
   325  	}
   326  	return
   327  }
   328  
   329  // main wraps Main so tests (which generate their own func main) can still run Main.
   330  func main() {
   331  	Main(nil, nil)
   332  }
   333  
   334  // Main sends on up when it's running, and shuts down when it receives from down.
   335  func Main(up chan<- struct{}, down <-chan struct{}) {
   336  	flag.Parse()
   337  
   338  	if *flagVersion {
   339  		fmt.Fprintf(os.Stderr, "camlistored version: %s\nGo version: %s (%s/%s)\n",
   340  			buildinfo.Version(), runtime.Version(), runtime.GOOS, runtime.GOARCH)
   341  		return
   342  	}
   343  	if *flagReindex {
   344  		index.SetImpendingReindex()
   345  	}
   346  
   347  	log.Printf("Starting camlistored version %s; Go %s (%s/%s)", buildinfo.Version(), runtime.Version(),
   348  		runtime.GOOS, runtime.GOARCH)
   349  
   350  	shutdownc := make(chan io.Closer, 1) // receives io.Closer to cleanly shut down
   351  	go handleSignals(shutdownc)
   352  
   353  	fileName, isNewConfig, err := findConfigFile(*flagConfigFile)
   354  	if err != nil {
   355  		exitf("Error finding config file %q: %v", fileName, err)
   356  	}
   357  	log.Printf("Using config file %s", fileName)
   358  	config, err := serverinit.Load(fileName)
   359  	if err != nil {
   360  		exitf("Could not load server config: %v", err)
   361  	}
   362  
   363  	ws := webserver.New()
   364  	listen, baseURL := listenAndBaseURL(config)
   365  
   366  	setupTLS(ws, config, listen)
   367  
   368  	err = ws.Listen(listen)
   369  	if err != nil {
   370  		exitf("Listen: %v", err)
   371  	}
   372  
   373  	if baseURL == "" {
   374  		baseURL = ws.ListenURL()
   375  	}
   376  
   377  	shutdownCloser, err := config.InstallHandlers(ws, baseURL, *flagReindex, nil)
   378  	if err != nil {
   379  		exitf("Error parsing config: %v", err)
   380  	}
   381  	shutdownc <- shutdownCloser
   382  
   383  	urlToOpen := baseURL
   384  	if !isNewConfig {
   385  		// user may like to configure the server at the initial startup,
   386  		// open UI if this is not the first run with a new config file.
   387  		urlToOpen += config.UIPath
   388  	}
   389  
   390  	log.Printf("Available on %s", urlToOpen)
   391  	if *flagOpenBrowser {
   392  		go osutil.OpenURL(urlToOpen)
   393  	}
   394  
   395  	go ws.Serve()
   396  	if flagPollParent {
   397  		osutil.DieOnParentDeath()
   398  	}
   399  
   400  	if err := config.StartApps(); err != nil {
   401  		exitf("StartApps: %v", err)
   402  	}
   403  
   404  	// Block forever, except during tests.
   405  	up <- struct{}{}
   406  	<-down
   407  	osExit(0)
   408  }