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 }