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 }