func parseConfig() { if android.OnAndroid() { return } configPath := osutil.UserClientConfigPath() if _, err := os.Stat(configPath); os.IsNotExist(err) { errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } conf, err := jsonconfig.ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) config = &clientConfig{ auth: cfg.OptionalString("auth", ""), server: cfg.OptionalString("server", ""), identity: cfg.OptionalString("identity", ""), identitySecretRing: cfg.OptionalString("identitySecretRing", osutil.IdentitySecretRing()), trustedCerts: cfg.OptionalList("trustedCerts"), ignoredFiles: cfg.OptionalList("ignoredFiles"), } if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }
func AddFlags() { defaultPath := "/x/y/z/we're/in-a-test" if !buildinfo.TestingLinked() { defaultPath = osutil.UserClientConfigPath() } flag.StringVar(&flagServer, "server", "", "Camlistore server prefix. If blank, the default from the \"server\" field of "+defaultPath+" is used. Acceptable forms: https://you.example.com, example.com:1345 (https assumed), or http://you.example.com/alt-root") osutil.AddSecretRingFlag() }
func getSignerPublicKeyBlobref() (signerRef blob.Ref, armored string, ok bool) { configOnce.Do(parseConfig) key := "keyId" keyId, ok := config[key].(string) if !ok { log.Printf("No key %q in JSON configuration file %q; have you run \"camput init\"?", key, osutil.UserClientConfigPath()) return } keyRing, hasKeyRing := config["secretRing"].(string) if !hasKeyRing { if fn := osutil.IdentitySecretRing(); fileExists(fn) { keyRing = fn } else if fn := jsonsign.DefaultSecRingPath(); fileExists(fn) { keyRing = fn } else { log.Printf("Couldn't find keyId %q; no 'secretRing' specified in config file, and no standard secret ring files exist.") return } } entity, err := jsonsign.EntityFromSecring(keyId, keyRing) if err != nil { log.Printf("Couldn't find keyId %q in secret ring: %v", keyId, err) return } armored, err = jsonsign.ArmoredPublicKey(entity) if err != nil { log.Printf("Error serializing public key: %v", err) return } // TODO(mpl): integrate with getSelfPubKeyDir if possible. selfPubKeyDir, ok := config["selfPubKeyDir"].(string) if !ok { selfPubKeyDir = osutil.KeyBlobsDir() log.Printf("No 'selfPubKeyDir' defined in %q, defaulting to %v", osutil.UserClientConfigPath(), selfPubKeyDir) } fi, err := os.Stat(selfPubKeyDir) if err != nil || !fi.IsDir() { log.Printf("selfPubKeyDir of %q doesn't exist or not a directory", selfPubKeyDir) return } br := blob.SHA1FromString(armored) pubFile := filepath.Join(selfPubKeyDir, br.String()+".camli") fi, err = os.Stat(pubFile) if err != nil { err = ioutil.WriteFile(pubFile, []byte(armored), 0644) if err != nil { log.Printf("Error writing public key to %q: %v", pubFile, err) return } } return br, armored, true }
func serverOrDie() string { if flagServer != "" { return cleanServer(flagServer) } configOnce.Do(parseConfig) server := cleanServer(config.server) if server == "" { log.Fatalf("Missing or invalid \"server\" in %q", osutil.UserClientConfigPath()) } return server }
func serverOrDie() string { if flagServer != "" { return cleanServer(flagServer) } configOnce.Do(parseConfig) value, ok := config["server"] var server string if ok { server = value.(string) } server = cleanServer(server) if !ok || server == "" { log.Fatalf("Missing or invalid \"server\" in %q", osutil.UserClientConfigPath()) } return server }
func parseConfig() { configPath := osutil.UserClientConfigPath() if _, err := os.Stat(configPath); os.IsNotExist(err) { errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } var err error if config, err = jsonconfig.ReadFile(configPath); err != nil { log.Fatal(err.Error()) return } }
func (c *initCmd) writeConfig(cc *clientconfig.Config) error { configFilePath := osutil.UserClientConfigPath() if _, err := os.Stat(configFilePath); err == nil { return fmt.Errorf("Config file %q already exists; quitting without touching it.", configFilePath) } if err := os.MkdirAll(filepath.Dir(configFilePath), 0700); err != nil { return err } jsonBytes, err := json.MarshalIndent(cc, "", " ") if err != nil { log.Fatalf("JSON serialization error: %v", err) } if err := ioutil.WriteFile(configFilePath, jsonBytes, 0600); err != nil { return fmt.Errorf("could not write client config file %v: %v", configFilePath, err) } log.Printf("Wrote %q; modify as necessary.", configFilePath) return nil }
// serverOrDie returns the server's URL found either as a command-line flag, // or as the default server in the config file. func serverOrDie() string { if s := os.Getenv("CAMLI_SERVER"); s != "" { return cleanServer(s) } if flagServer != "" { if !isURLOrHostPort(flagServer) { configOnce.Do(parseConfig) serverConf, ok := config.Servers[flagServer] if ok { return serverConf.Server } log.Printf("%q looks like a server alias, but no such alias found in config.", flagServer) } else { return cleanServer(flagServer) } } server := defaultServer() if server == "" { log.Fatalf("No valid server defined with CAMLI_SERVER, or with -server, or in %q", osutil.UserClientConfigPath()) } return cleanServer(server) }
func AddFlags() { defaultPath := osutil.UserClientConfigPath() flag.StringVar(&flagServer, "server", "", "Camlistore server prefix. If blank, the default from the \"server\" field of "+defaultPath+" is used. Acceptable forms: https://you.example.com, example.com:1345 (https assumed), or http://you.example.com/alt-root") flag.StringVar(&flagSecretRing, "secret-keyring", "", "GnuPG secret keyring file to use.") }
func (c *Client) initSignerPublicKeyBlobref() { configOnce.Do(parseConfig) keyId := config.identity if keyId == "" { log.Fatalf("No 'identity' key in JSON configuration file %q; have you run \"camput init\"?", osutil.UserClientConfigPath()) } keyRing := c.SecretRingFile() if !fileExists(keyRing) { log.Fatalf("Could not find keyId %q, because secret ring file %q does not exist.", keyId, keyRing) } entity, err := jsonsign.EntityFromSecring(keyId, keyRing) if err != nil { log.Fatalf("Couldn't find keyId %q in secret ring %v: %v", keyId, keyRing, err) } armored, err := jsonsign.ArmoredPublicKey(entity) if err != nil { log.Fatalf("Error serializing public key: %v", err) } // TODO(mpl): completely get rid of it if possible // http://camlistore.org/issue/377 selfPubKeyDir := osutil.KeyBlobsDir() fi, err := os.Stat(selfPubKeyDir) if err != nil || !fi.IsDir() { log.Fatalf("selfPubKeyDir as %q doesn't exist or not a directory", selfPubKeyDir) } br := blob.SHA1FromString(armored) pubFile := filepath.Join(selfPubKeyDir, br.String()+".camli") fi, err = os.Stat(pubFile) if err != nil { if !os.IsNotExist(err) { log.Fatalf("Could not stat %q: %v", pubFile, err) } err = ioutil.WriteFile(pubFile, []byte(armored), 0644) if err != nil { log.Fatalf("Error writing public key to %q: %v", pubFile, err) } } c.signerPublicKeyRef = br c.publicKeyArmored = armored }
func (c *initCmd) RunCommand(args []string) error { if len(args) > 0 { return cmdmain.ErrUsage } if c.newKey && c.gpgkey != "" { log.Fatal("--newkey and --gpgkey are mutually exclusive") } blobDir := osutil.KeyBlobsDir() if err := os.MkdirAll(blobDir, 0700); err != nil { return err } var keyId string var err error secRing := osutil.IdentitySecretRing() if c.newKey { keyId, err = jsonsign.GenerateNewSecRing(secRing) if err != nil { return err } } else { keyId, err = c.keyId(secRing) if err != nil { return err } } pubArmor, err := c.getPublicKeyArmored(keyId) if err != nil { return err } bref := blob.SHA1FromString(string(pubArmor)) keyBlobPath := path.Join(blobDir, bref.String()+".camli") if err = ioutil.WriteFile(keyBlobPath, pubArmor, 0644); err != nil { log.Fatalf("Error writing public key blob to %q: %v", keyBlobPath, err) } if ok, err := jsonsign.VerifyPublicKeyFile(keyBlobPath, keyId); !ok { log.Fatalf("Error verifying public key at %q: %v", keyBlobPath, err) } log.Printf("Your Camlistore identity (your GPG public key's blobref) is: %s", bref.String()) if c.noconfig { return nil } configFilePath := osutil.UserClientConfigPath() _, err = os.Stat(configFilePath) if err == nil { log.Fatalf("Config file %q already exists; quitting without touching it.", configFilePath) } if f, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600); err == nil { defer f.Close() m := &clientconfig.Config{ Servers: map[string]*clientconfig.Server{ "localhost": { Server: "http://localhost:3179", IsDefault: true, Auth: "localhost", }, }, Identity: keyId, IgnoredFiles: []string{".DS_Store"}, } jsonBytes, err := json.MarshalIndent(m, "", " ") if err != nil { log.Fatalf("JSON serialization error: %v", err) } _, err = f.Write(jsonBytes) if err != nil { log.Fatalf("Error writing to %q: %v", configFilePath, err) } log.Printf("Wrote %q; modify as necessary.", configFilePath) } return nil }
// New returns a new Camlistore Client. // The provided server is either "host:port" (assumed http, not https) or a URL prefix, with or without a path, or a server alias from the client configuration file. A server alias should not be confused with a hostname, therefore it cannot contain any colon or period. // Errors are not returned until subsequent operations. func New(server string, opts ...ClientOption) *Client { if !isURLOrHostPort(server) { configOnce.Do(parseConfig) serverConf, ok := config.Servers[server] if !ok { log.Fatalf("%q looks like a server alias, but no such alias found in config at %v", server, osutil.UserClientConfigPath()) } server = serverConf.Server } return newClient(server, auth.None{}, opts...) }
// New returns a new Camlistore Client. // The provided server is either "host:port" (assumed http, not https) or a URL prefix, with or without a path, or a server alias from the client configuration file. A server alias should not be confused with a hostname, therefore it cannot contain any colon or period. // Errors are not returned until subsequent operations. func New(server string) *Client { if !isURLOrHostPort(server) { configOnce.Do(parseConfig) serverConf, ok := config.Servers[server] if !ok { log.Fatalf("%q looks like a server alias, but no such alias found in config at %v", server, osutil.UserClientConfigPath()) } server = serverConf.Server } return &Client{ server: server, httpClient: http.DefaultClient, reqGate: make(chan bool, maxParallelHTTP), haveCache: noHaveCache{}, log: log.New(os.Stderr, "", log.Ldate|log.Ltime), authMode: auth.None{}, } }
func (c *initCmd) RunCommand(args []string) error { if len(args) > 0 { return cmdmain.ErrUsage } var err error if c.dumpJSON { type jsonConfig struct { Identity_secring *jsonsign.IdentitySecring Client_config *clientconfig.Config Server_config *serverconfig.Config } var config jsonConfig // generate a new secring struct config.Identity_secring, err = jsonsign.GenerateNewSecRingStruct() if err != nil { return err } c.keyId = config.Identity_secring.KeyId // generate a new server config struct config.Server_config = GenerateServerConfig(c.keyId) // generate a new client config struct config.Client_config = GenerateClientConfig(c.keyId) jsonBytes, err := json.MarshalIndent(config, "", " ") if err != nil { log.Fatalf("JSON serialization error: %v", err) } //log.Printf("%+#v\n", string(jsonBytes)) _, err = os.Stdout.Write(jsonBytes) return err } if c.newKey && c.keyId != "" { log.Fatal("--newkey and --gpgkey are mutually exclusive") } if c.newKey { c.secretRing = osutil.DefaultSecretRingFile() c.keyId, err = jsonsign.GenerateNewSecRing(c.secretRing) if err != nil { return err } } else { if err := c.initSecretRing(); err != nil { return err } if err := c.initKeyId(); err != nil { return err } } pubArmor, err := c.getPublicKeyArmored() if err != nil { return err } bref := blob.SHA1FromString(string(pubArmor)) log.Printf("Your Camlistore identity (your GPG public key's blobref) is: %s", bref.String()) if c.noconfig { return nil } configFilePath := osutil.UserClientConfigPath() _, err = os.Stat(configFilePath) if err == nil { log.Fatalf("Config file %q already exists; quitting without touching it.", configFilePath) } if err := os.MkdirAll(filepath.Dir(configFilePath), 0700); err != nil { return err } if f, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600); err == nil { defer f.Close() // refactored to a service routine m := GenerateClientConfig(c.keyId) jsonBytes, err := json.MarshalIndent(m, "", " ") if err != nil { log.Fatalf("JSON serialization error: %v", err) } _, err = f.Write(jsonBytes) if err != nil { log.Fatalf("Error writing to %q: %v", configFilePath, err) } log.Printf("Wrote %q; modify as necessary.", configFilePath) } else { return fmt.Errorf("could not write client config file %v: %v", configFilePath, err) } return nil }
package camtypes import ( "fmt" "log" "camlistore.org/pkg/osutil" ) // TODO(mpl): move pkg/camerrors stuff in here var camErrors = map[string]*camErr{} var ( ErrClientNoServer = addCamError("client-no-server", funcStr(func() string { return fmt.Sprintf("No valid server defined. It can be set with the CAMLI_SERVER environment variable, or the --server flag, or in the \"servers\" section of %q (see https://camlistore.org/docs/client-config).", osutil.UserClientConfigPath()) })) ErrClientNoPublicKey = addCamError("client-no-public-key", str("No public key configured: see 'camput init'.")) ) type str string func (s str) String() string { return string(s) } type funcStr func() string func (f funcStr) String() string { return f() } type camErr struct { key string des fmt.Stringer
// printConfigChangeHelp checks if conf contains obsolete keys, // and prints additional help in this case. func printConfigChangeHelp(conf jsonconfig.Obj) { // rename maps from old key names to the new ones. // If there is no new one, the value is the empty string. rename := map[string]string{ "keyId": "identity", "publicKeyBlobref": "", "selfPubKeyDir": "", "secretRing": "identitySecretRing", } oldConfig := false configChangedMsg := fmt.Sprintf("The client configuration file (%s) keys have changed.\n", osutil.UserClientConfigPath()) for _, unknown := range conf.UnknownKeys() { v, ok := rename[unknown] if ok { if v != "" { configChangedMsg += fmt.Sprintf("%q should be renamed %q.\n", unknown, v) } else { configChangedMsg += fmt.Sprintf("%q should be removed.\n", unknown) } oldConfig = true } } if oldConfig { configChangedMsg += "Please see http://camlistore.org/docs/client-config, or use camput init to recreate a default one." log.Print(configChangedMsg) } }
func (c *Client) initSignerPublicKeyBlobref() { if c.paramsOnly { log.Print("client: paramsOnly set; cannot get public key from config or env vars.") return } keyId := os.Getenv("CAMLI_KEYID") if keyId == "" { configOnce.Do(parseConfig) keyId = config.Identity if keyId == "" { log.Fatalf("No 'identity' key in JSON configuration file %q; have you run \"camput init\"?", osutil.UserClientConfigPath()) } } keyRing := c.SecretRingFile() if !fileExists(keyRing) { log.Fatalf("Could not find keyId %q, because secret ring file %q does not exist.", keyId, keyRing) } entity, err := jsonsign.EntityFromSecring(keyId, keyRing) if err != nil { log.Fatalf("Couldn't find keyId %q in secret ring %v: %v", keyId, keyRing, err) } armored, err := jsonsign.ArmoredPublicKey(entity) if err != nil { log.Fatalf("Error serializing public key: %v", err) } c.signerPublicKeyRef = blob.SHA1FromString(armored) c.publicKeyArmored = armored }
func (c *initCmd) RunCommand(args []string) error { if len(args) > 0 { return cmdmain.ErrUsage } if c.newKey && c.gpgkey != "" { log.Fatal("--newkey and --gpgkey are mutually exclusive") } blobDir := path.Join(osutil.CamliConfigDir(), "keyblobs") os.Mkdir(osutil.CamliConfigDir(), 0700) os.Mkdir(blobDir, 0700) var keyId string var err error secRing := osutil.IdentitySecretRing() if c.newKey { keyId, err = jsonsign.GenerateNewSecRing(secRing) if err != nil { return err } } else { keyId, err = c.keyId(secRing) if err != nil { return err } } if os.Getenv("GPG_AGENT_INFO") == "" { log.Printf("No GPG_AGENT_INFO found in environment; you should setup gnupg-agent. camput might be annoying otherwise, if your private key is encrypted.") } pubArmor, err := c.getPublicKeyArmored(keyId) if err != nil { return err } bref := blobref.SHA1FromString(string(pubArmor)) keyBlobPath := path.Join(blobDir, bref.String()+".camli") if err = ioutil.WriteFile(keyBlobPath, pubArmor, 0644); err != nil { log.Fatalf("Error writing public key blob to %q: %v", keyBlobPath, err) } if ok, err := jsonsign.VerifyPublicKeyFile(keyBlobPath, keyId); !ok { log.Fatalf("Error verifying public key at %q: %v", keyBlobPath, err) } log.Printf("Your Camlistore identity (your GPG public key's blobref) is: %s", bref.String()) configFilePath := osutil.UserClientConfigPath() _, err = os.Stat(configFilePath) if err == nil { log.Fatalf("Config file %q already exists; quitting without touching it.", configFilePath) } if f, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600); err == nil { defer f.Close() m := make(map[string]interface{}) m["keyId"] = keyId // TODO(bradfitz): make this 'identity' to match server config? m["publicKeyBlobref"] = bref.String() // TODO(bradfitz): not used anymore? m["server"] = "http://localhost:3179/" m["selfPubKeyDir"] = blobDir m["auth"] = "localhost" jsonBytes, err := json.MarshalIndent(m, "", " ") if err != nil { log.Fatalf("JSON serialization error: %v", err) } _, err = f.Write(jsonBytes) if err != nil { log.Fatalf("Error writing to %q: %v", configFilePath, err) } log.Printf("Wrote %q; modify as necessary.", configFilePath) } return nil }
// lazy config parsing when there's a known client already. // The client c may be nil. func (c *Client) parseConfig() { if android.OnAndroid() { panic("parseConfig should never have been called on Android") } if configDisabled { panic("parseConfig should never have been called with CAMLI_DISABLE_CLIENT_CONFIG_FILE set") } configPath := osutil.UserClientConfigPath() if _, err := wkfs.Stat(configPath); os.IsNotExist(err) { if c != nil && c.isSharePrefix { return } errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath) if keyId := serverKeyId(); keyId != "" { hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyId, osutil.UserServerConfigPath(), keyId) errMsg += hint } log.Fatal(errMsg) } // TODO: instead of using jsonconfig, we could read the file, and unmarshall into the structs that we now have in pkg/types/clientconfig. But we'll have to add the old fields (before the name changes, and before the multi-servers change) to the structs as well for our gracefull conversion/error messages to work. conf, err := osutil.NewJSONConfigParser().ReadFile(configPath) if err != nil { log.Fatal(err.Error()) } cfg := jsonconfig.Obj(conf) if singleServerAuth := cfg.OptionalString("auth", ""); singleServerAuth != "" { newConf, err := convertToMultiServers(cfg) if err != nil { log.Print(err) } else { cfg = newConf } } config = &clientconfig.Config{ Identity: cfg.OptionalString("identity", ""), IdentitySecretRing: cfg.OptionalString("identitySecretRing", ""), IgnoredFiles: cfg.OptionalList("ignoredFiles"), } serversList := make(map[string]*clientconfig.Server) servers := cfg.OptionalObject("servers") for alias, vei := range servers { // An alias should never be confused with a host name, // so we forbid anything looking like one. if isURLOrHostPort(alias) { log.Fatalf("Server alias %q looks like a hostname; \".\" or \";\" are not allowed.", alias) } serverMap, ok := vei.(map[string]interface{}) if !ok { log.Fatalf("entry %q in servers section is a %T, want an object", alias, vei) } serverConf := jsonconfig.Obj(serverMap) server := &clientconfig.Server{ Server: cleanServer(serverConf.OptionalString("server", "")), Auth: serverConf.OptionalString("auth", ""), IsDefault: serverConf.OptionalBool("default", false), TrustedCerts: serverConf.OptionalList("trustedCerts"), } if err := serverConf.Validate(); err != nil { log.Fatalf("Error in servers section of config file for server %q: %v", alias, err) } serversList[alias] = server } config.Servers = serversList if err := cfg.Validate(); err != nil { printConfigChangeHelp(cfg) log.Fatalf("Error in config file: %v", err) } }
// New returns a new Camlistore Client. // The provided server is either "host:port" (assumed http, not https) or a URL prefix, with or without a path, or a server alias from the client configuration file. A server alias should not be confused with a hostname, therefore it cannot contain any colon or period. // Errors are not returned until subsequent operations. func New(server string) *Client { if !isURLOrHostPort(server) { configOnce.Do(parseConfig) serverConf, ok := config.Servers[server] if !ok { log.Fatalf("%q looks like a server alias, but no such alias found in config at %v", server, osutil.UserClientConfigPath()) } server = serverConf.Server } httpClient := &http.Client{ Transport: &http.Transport{ MaxIdleConnsPerHost: maxParallelHTTP, }, } return &Client{ server: server, httpClient: httpClient, httpGate: syncutil.NewGate(maxParallelHTTP), haveCache: noHaveCache{}, log: log.New(os.Stderr, "", log.Ldate|log.Ltime), authMode: auth.None{}, } }