github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/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  	"database/sql"
    26  	"encoding/json"
    27  	"encoding/pem"
    28  	"flag"
    29  	"fmt"
    30  	"io"
    31  	"io/ioutil"
    32  	"log"
    33  	"math/big"
    34  	"net"
    35  	"os"
    36  	"os/signal"
    37  	"path/filepath"
    38  	"runtime"
    39  	"strconv"
    40  	"strings"
    41  	"syscall"
    42  	"time"
    43  
    44  	"camlistore.org/pkg/buildinfo"
    45  	"camlistore.org/pkg/jsonsign"
    46  	"camlistore.org/pkg/misc"
    47  	"camlistore.org/pkg/osutil"
    48  	"camlistore.org/pkg/serverconfig"
    49  	"camlistore.org/pkg/webserver"
    50  
    51  	// Storage options:
    52  	_ "camlistore.org/pkg/blobserver/cond"
    53  	_ "camlistore.org/pkg/blobserver/diskpacked"
    54  	_ "camlistore.org/pkg/blobserver/encrypt"
    55  	_ "camlistore.org/pkg/blobserver/google/cloudstorage"
    56  	_ "camlistore.org/pkg/blobserver/google/drive"
    57  	_ "camlistore.org/pkg/blobserver/localdisk"
    58  	_ "camlistore.org/pkg/blobserver/remote"
    59  	_ "camlistore.org/pkg/blobserver/replica"
    60  	_ "camlistore.org/pkg/blobserver/s3"
    61  	_ "camlistore.org/pkg/blobserver/shard"
    62  	// Indexers: (also present themselves as storage targets)
    63  	// sqlite is taken care of in option_sqlite.go
    64  	"camlistore.org/pkg/index" // base indexer + in-memory dev index
    65  	_ "camlistore.org/pkg/index/kvfile"
    66  	_ "camlistore.org/pkg/index/mongo"
    67  	_ "camlistore.org/pkg/index/mysql"
    68  	_ "camlistore.org/pkg/index/postgres"
    69  	// KeyValue implementations:
    70  	_ "camlistore.org/pkg/sorted"
    71  	_ "camlistore.org/pkg/sorted/kvfile"
    72  	_ "camlistore.org/pkg/sorted/mongo"
    73  	_ "camlistore.org/pkg/sorted/mysql"
    74  	_ "camlistore.org/pkg/sorted/postgres"
    75  	"camlistore.org/pkg/sorted/sqlite"
    76  
    77  	// Handlers:
    78  	_ "camlistore.org/pkg/search"
    79  	_ "camlistore.org/pkg/server" // UI, publish, etc
    80  
    81  	// Importers:
    82  	_ "camlistore.org/pkg/importer/dummy"
    83  	_ "camlistore.org/pkg/importer/flickr"
    84  	_ "camlistore.org/pkg/importer/foursquare"
    85  )
    86  
    87  var (
    88  	flagVersion    = flag.Bool("version", false, "show version")
    89  	flagConfigFile = flag.String("configfile", "",
    90  		"Config file to use, relative to the Camlistore configuration directory root. If blank, the default is used or auto-generated.")
    91  	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.")
    92  	flagOpenBrowser = flag.Bool("openbrowser", true, "Launches the UI on startup")
    93  	flagReindex     = flag.Bool("reindex", false, "Reindex all blobs on startup")
    94  	flagPollParent  bool
    95  )
    96  
    97  func init() {
    98  	if debug, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG")); debug {
    99  		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.")
   100  	}
   101  }
   102  
   103  func exitf(pattern string, args ...interface{}) {
   104  	if !strings.HasSuffix(pattern, "\n") {
   105  		pattern = pattern + "\n"
   106  	}
   107  	fmt.Fprintf(os.Stderr, pattern, args...)
   108  	osExit(1)
   109  }
   110  
   111  // 1) We do not want to force the user to buy a cert.
   112  // 2) We still want our client (camput) to be able to
   113  // verify the cert's authenticity.
   114  // 3) We want to avoid MITM attacks and warnings in
   115  // the browser.
   116  // Using a simple self-signed won't do because of 3),
   117  // as Chrome offers no way to set a self-signed as
   118  // trusted when importing it. (same on android).
   119  // We could have created a self-signed CA (that we
   120  // would import in the browsers) and create another
   121  // cert (signed by that CA) which would be the one
   122  // used in camlistore.
   123  // We're doing even simpler: create a self-signed
   124  // CA and directly use it as a self-signed cert
   125  // (and install it as a CA in the browsers).
   126  // 2) is satisfied by doing our own checks,
   127  // See pkg/client
   128  func genSelfTLS(listen string) error {
   129  	priv, err := rsa.GenerateKey(rand.Reader, 1024)
   130  	if err != nil {
   131  		return fmt.Errorf("failed to generate private key: %s", err)
   132  	}
   133  
   134  	now := time.Now()
   135  
   136  	hostname, _, err := net.SplitHostPort(listen)
   137  	if err != nil {
   138  		return fmt.Errorf("splitting listen failed: %q", err)
   139  	}
   140  
   141  	// TODO(mpl): if no host is specified in the listening address
   142  	// (e.g ":3179") we'll end up in this case, and the self-signed
   143  	// will have "localhost" as a CommonName. But I don't think
   144  	// there's anything we can do about it. Maybe warn...
   145  	if hostname == "" {
   146  		hostname = "localhost"
   147  	}
   148  	template := x509.Certificate{
   149  		SerialNumber: new(big.Int).SetInt64(0),
   150  		Subject: pkix.Name{
   151  			CommonName:   hostname,
   152  			Organization: []string{hostname},
   153  		},
   154  		NotBefore:    now.Add(-5 * time.Minute).UTC(),
   155  		NotAfter:     now.AddDate(1, 0, 0).UTC(),
   156  		SubjectKeyId: []byte{1, 2, 3, 4},
   157  		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   158  		IsCA:         true,
   159  		BasicConstraintsValid: true,
   160  	}
   161  
   162  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
   163  	if err != nil {
   164  		return fmt.Errorf("Failed to create certificate: %s", err)
   165  	}
   166  
   167  	defCert := osutil.DefaultTLSCert()
   168  	defKey := osutil.DefaultTLSKey()
   169  	certOut, err := os.Create(defCert)
   170  	if err != nil {
   171  		return fmt.Errorf("failed to open %s for writing: %s", defCert, err)
   172  	}
   173  	pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
   174  	certOut.Close()
   175  	log.Printf("written %s\n", defCert)
   176  	cert, err := x509.ParseCertificate(derBytes)
   177  	if err != nil {
   178  		return fmt.Errorf("Failed to parse certificate: %v", err)
   179  	}
   180  	sig := misc.SHA256Prefix(cert.Raw)
   181  	hint := "You must add this certificate's fingerprint to your client's trusted certs list to use it. Like so:\n" +
   182  		`"trustedCerts": ["` + sig + `"],`
   183  	log.Printf(hint)
   184  
   185  	keyOut, err := os.OpenFile(defKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
   186  	if err != nil {
   187  		return fmt.Errorf("failed to open %s for writing:", defKey, err)
   188  	}
   189  	pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
   190  	keyOut.Close()
   191  	log.Printf("written %s\n", defKey)
   192  	return nil
   193  }
   194  
   195  // findConfigFile returns the absolute path of the user's
   196  // config file.
   197  // The provided file may be absolute or relative
   198  // to the user's configuration directory.
   199  // If file is empty, a default high-level config is written
   200  // for the user.
   201  func findConfigFile(file string) (absPath string, isNewConfig bool, err error) {
   202  	switch {
   203  	case file == "":
   204  		absPath = osutil.UserServerConfigPath()
   205  		_, err = os.Stat(absPath)
   206  		if os.IsNotExist(err) {
   207  			err = os.MkdirAll(osutil.CamliConfigDir(), 0700)
   208  			if err != nil {
   209  				return
   210  			}
   211  			log.Printf("Generating template config file %s", absPath)
   212  			if err = newDefaultConfigFile(absPath); err == nil {
   213  				isNewConfig = true
   214  			}
   215  		}
   216  		return
   217  	case filepath.IsAbs(file):
   218  		absPath = file
   219  	default:
   220  		absPath = filepath.Join(osutil.CamliConfigDir(), file)
   221  	}
   222  	_, err = os.Stat(absPath)
   223  	return
   224  }
   225  
   226  type defaultConfigFile struct {
   227  	Listen             string        `json:"listen"`
   228  	HTTPS              bool          `json:"https"`
   229  	Auth               string        `json:"auth"`
   230  	Identity           string        `json:"identity"`
   231  	IdentitySecretRing string        `json:"identitySecretRing"`
   232  	KVFile             string        `json:"kvIndexFile"`
   233  	BlobPath           string        `json:"blobPath"`
   234  	MySQL              string        `json:"mysql"`
   235  	Mongo              string        `json:"mongo"`
   236  	Postgres           string        `json:"postgres"`
   237  	SQLite             string        `json:"sqlite"`
   238  	S3                 string        `json:"s3"`
   239  	ReplicateTo        []interface{} `json:"replicateTo"`
   240  	Publish            struct{}      `json:"publish"`
   241  }
   242  
   243  var defaultListenAddr = ":3179"
   244  
   245  func newDefaultConfigFile(path string) error {
   246  	conf := defaultConfigFile{
   247  		Listen:      defaultListenAddr,
   248  		HTTPS:       false,
   249  		Auth:        "localhost",
   250  		ReplicateTo: make([]interface{}, 0),
   251  	}
   252  	blobDir := osutil.CamliBlobRoot()
   253  	if err := os.MkdirAll(blobDir, 0700); err != nil {
   254  		return fmt.Errorf("Could not create default blobs directory: %v", err)
   255  	}
   256  	conf.BlobPath = blobDir
   257  	if sqlite.CompiledIn() {
   258  		conf.SQLite = filepath.Join(osutil.CamliVarDir(), "camli-index.db")
   259  		if fi, err := os.Stat(conf.SQLite); os.IsNotExist(err) || (fi != nil && fi.Size() == 0) {
   260  			if err := initSQLiteDB(conf.SQLite); err != nil {
   261  				log.Printf("Error initializing DB %s: %v", conf.SQLite, err)
   262  			}
   263  		}
   264  	} else {
   265  		conf.KVFile = filepath.Join(osutil.CamliVarDir(), "camli-index.kvdb")
   266  	}
   267  
   268  	var keyId string
   269  	secRing := osutil.IdentitySecretRing()
   270  	_, err := os.Stat(secRing)
   271  	switch {
   272  	case err == nil:
   273  		keyId, err = jsonsign.KeyIdFromRing(secRing)
   274  		if err != nil {
   275  			return fmt.Errorf("Could not find any keyId in file %q: %v", secRing, err)
   276  		}
   277  		log.Printf("Re-using identity with keyId %q found in file %s", keyId, secRing)
   278  	case os.IsNotExist(err):
   279  		keyId, err = jsonsign.GenerateNewSecRing(secRing)
   280  		if err != nil {
   281  			return fmt.Errorf("Could not generate new secRing at file %q: %v", secRing, err)
   282  		}
   283  		log.Printf("Generated new identity with keyId %q in file %s", keyId, secRing)
   284  	}
   285  	if err != nil {
   286  		return fmt.Errorf("Could not stat secret ring %q: %v", secRing, err)
   287  	}
   288  	conf.Identity = keyId
   289  	conf.IdentitySecretRing = secRing
   290  
   291  	confData, err := json.MarshalIndent(conf, "", "    ")
   292  	if err != nil {
   293  		return fmt.Errorf("Could not json encode config file : %v", err)
   294  	}
   295  
   296  	if err := ioutil.WriteFile(path, confData, 0600); err != nil {
   297  		return fmt.Errorf("Could not create or write default server config: %v", err)
   298  	}
   299  
   300  	return nil
   301  }
   302  
   303  func initSQLiteDB(path string) error {
   304  	db, err := sql.Open("sqlite3", path)
   305  	if err != nil {
   306  		return err
   307  	}
   308  	defer db.Close()
   309  	for _, tableSql := range sqlite.SQLCreateTables() {
   310  		if _, err := db.Exec(tableSql); err != nil {
   311  			return err
   312  		}
   313  	}
   314  	if sqlite.IsWALCapable() {
   315  		if _, err := db.Exec(sqlite.EnableWAL()); err != nil {
   316  			return err
   317  		}
   318  	} else {
   319  		log.Print("WARNING: An SQLite indexer without Write Ahead Logging will most likely fail. See http://camlistore.org/issues/114\n")
   320  	}
   321  	_, err = db.Exec(fmt.Sprintf(`REPLACE INTO meta VALUES ('version', '%d')`, sqlite.SchemaVersion()))
   322  	return err
   323  }
   324  
   325  func setupTLS(ws *webserver.Server, config *serverconfig.Config, listen string) {
   326  	cert, key := config.OptionalString("TLSCertFile", ""), config.OptionalString("TLSKeyFile", "")
   327  	if !config.OptionalBool("https", true) {
   328  		return
   329  	}
   330  	if (cert != "") != (key != "") {
   331  		exitf("TLSCertFile and TLSKeyFile must both be either present or absent")
   332  	}
   333  
   334  	defCert := osutil.DefaultTLSCert()
   335  	defKey := osutil.DefaultTLSKey()
   336  	if cert == defCert && key == defKey {
   337  		_, err1 := os.Stat(cert)
   338  		_, err2 := os.Stat(key)
   339  		if err1 != nil || err2 != nil {
   340  			if os.IsNotExist(err1) || os.IsNotExist(err2) {
   341  				if err := genSelfTLS(listen); err != nil {
   342  					exitf("Could not generate self-signed TLS cert: %q", err)
   343  				}
   344  			} else {
   345  				exitf("Could not stat cert or key: %q, %q", err1, err2)
   346  			}
   347  		}
   348  	}
   349  	if cert == "" && key == "" {
   350  		err := genSelfTLS(listen)
   351  		if err != nil {
   352  			exitf("Could not generate self signed creds: %q", err)
   353  		}
   354  		cert = defCert
   355  		key = defKey
   356  	}
   357  	data, err := ioutil.ReadFile(cert)
   358  	if err != nil {
   359  		exitf("Failed to read pem certificate: %s", err)
   360  	}
   361  	block, _ := pem.Decode(data)
   362  	if block == nil {
   363  		exitf("Failed to decode pem certificate")
   364  	}
   365  	certif, err := x509.ParseCertificate(block.Bytes)
   366  	if err != nil {
   367  		exitf("Failed to parse certificate: %v", err)
   368  	}
   369  	sig := misc.SHA256Prefix(certif.Raw)
   370  	log.Printf("TLS enabled, with SHA-256 certificate fingerprint: %v", sig)
   371  	ws.SetTLS(cert, key)
   372  }
   373  
   374  var osExit = os.Exit // testing hook
   375  
   376  func handleSignals(shutdownc <-chan io.Closer) {
   377  	c := make(chan os.Signal, 1)
   378  	signal.Notify(c, syscall.SIGHUP)
   379  	signal.Notify(c, syscall.SIGINT)
   380  	for {
   381  		sig := <-c
   382  		sysSig, ok := sig.(syscall.Signal)
   383  		if !ok {
   384  			log.Fatal("Not a unix signal")
   385  		}
   386  		switch sysSig {
   387  		case syscall.SIGHUP:
   388  			log.Print("SIGHUP: restarting camli")
   389  			err := osutil.RestartProcess()
   390  			if err != nil {
   391  				log.Fatal("Failed to restart: " + err.Error())
   392  			}
   393  		case syscall.SIGINT:
   394  			log.Print("Got SIGINT: shutting down")
   395  			donec := make(chan bool)
   396  			go func() {
   397  				cl := <-shutdownc
   398  				if err := cl.Close(); err != nil {
   399  					exitf("Error shutting down: %v", err)
   400  				}
   401  				donec <- true
   402  			}()
   403  			select {
   404  			case <-donec:
   405  				log.Printf("Shut down.")
   406  				osExit(0)
   407  			case <-time.After(2 * time.Second):
   408  				exitf("Timeout shutting down. Exiting uncleanly.")
   409  			}
   410  		default:
   411  			log.Fatal("Received another signal, should not happen.")
   412  		}
   413  	}
   414  }
   415  
   416  // listenAndBaseURL finds the configured, default, or inferred listen address
   417  // and base URL from the command-line flags and provided config.
   418  func listenAndBaseURL(config *serverconfig.Config) (listen, baseURL string) {
   419  	baseURL = config.OptionalString("baseURL", "")
   420  	listen = *listenFlag
   421  	listenConfig := config.OptionalString("listen", "")
   422  	// command-line takes priority over config
   423  	if listen == "" {
   424  		listen = listenConfig
   425  		if listen == "" {
   426  			exitf("\"listen\" needs to be specified either in the config or on the command line")
   427  		}
   428  	}
   429  	return
   430  }
   431  
   432  // main wraps Main so tests (which generate their own func main) can still run Main.
   433  func main() {
   434  	Main(nil, nil)
   435  }
   436  
   437  // Main sends on up when it's running, and shuts down when it receives from down.
   438  func Main(up chan<- struct{}, down <-chan struct{}) {
   439  	flag.Parse()
   440  
   441  	if *flagVersion {
   442  		fmt.Fprintf(os.Stderr, "camlistored version: %s\nGo version: %s (%s/%s)\n",
   443  			buildinfo.Version(), runtime.Version(), runtime.GOOS, runtime.GOARCH)
   444  		return
   445  	}
   446  	if *flagReindex {
   447  		index.SetImpendingReindex()
   448  	}
   449  
   450  	log.Printf("Starting camlistored version %s; Go %s (%s/%s)", buildinfo.Version(), runtime.Version(),
   451  		runtime.GOOS, runtime.GOARCH)
   452  
   453  	shutdownc := make(chan io.Closer, 1) // receives io.Closer to cleanly shut down
   454  	go handleSignals(shutdownc)
   455  
   456  	fileName, isNewConfig, err := findConfigFile(*flagConfigFile)
   457  	if err != nil {
   458  		exitf("Error finding config file %q: %v", fileName, err)
   459  	}
   460  	log.Printf("Using config file %s", fileName)
   461  	config, err := serverconfig.Load(fileName)
   462  	if err != nil {
   463  		exitf("Could not load server config: %v", err)
   464  	}
   465  
   466  	ws := webserver.New()
   467  	listen, baseURL := listenAndBaseURL(config)
   468  
   469  	setupTLS(ws, config, listen)
   470  
   471  	err = ws.Listen(listen)
   472  	if err != nil {
   473  		exitf("Listen: %v", err)
   474  	}
   475  
   476  	if baseURL == "" {
   477  		baseURL = ws.ListenURL()
   478  	}
   479  
   480  	shutdownCloser, err := config.InstallHandlers(ws, baseURL, *flagReindex, nil)
   481  	if err != nil {
   482  		exitf("Error parsing config: %v", err)
   483  	}
   484  	shutdownc <- shutdownCloser
   485  
   486  	urlToOpen := baseURL
   487  	if !isNewConfig {
   488  		// user may like to configure the server at the initial startup,
   489  		// open UI if this is not the first run with a new config file.
   490  		urlToOpen += config.UIPath
   491  	}
   492  
   493  	log.Printf("Available on %s", urlToOpen)
   494  	if *flagOpenBrowser {
   495  		go osutil.OpenURL(urlToOpen)
   496  	}
   497  
   498  	go ws.Serve()
   499  	if flagPollParent {
   500  		osutil.DieOnParentDeath()
   501  	}
   502  
   503  	// Block forever, except during tests.
   504  	up <- struct{}{}
   505  	<-down
   506  	osExit(0)
   507  }