github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/cmd/siad/daemon.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "os/signal" 8 "strconv" 9 "strings" 10 "syscall" 11 "time" 12 13 "github.com/Synthesix/Sia/build" 14 "github.com/Synthesix/Sia/crypto" 15 "github.com/Synthesix/Sia/modules" 16 "github.com/Synthesix/Sia/profile" 17 mnemonics "github.com/NebulousLabs/entropy-mnemonics" 18 19 "github.com/spf13/cobra" 20 "golang.org/x/crypto/ssh/terminal" 21 ) 22 23 // passwordPrompt securely reads a password from stdin. 24 func passwordPrompt(prompt string) (string, error) { 25 fmt.Print(prompt) 26 pw, err := terminal.ReadPassword(int(syscall.Stdin)) 27 fmt.Println() 28 return string(pw), err 29 } 30 31 // verifyAPISecurity checks that the security values are consistent with a 32 // sane, secure system. 33 func verifyAPISecurity(config Config) error { 34 // Make sure that only the loopback address is allowed unless the 35 // --disable-api-security flag has been used. 36 if !config.Siad.AllowAPIBind { 37 addr := modules.NetAddress(config.Siad.APIaddr) 38 if !addr.IsLoopback() { 39 if addr.Host() == "" { 40 return fmt.Errorf("a blank host will listen on all interfaces, did you mean localhost:%v?\nyou must pass --disable-api-security to bind Siad to a non-localhost address", addr.Port()) 41 } 42 return errors.New("you must pass --disable-api-security to bind Siad to a non-localhost address") 43 } 44 return nil 45 } 46 47 // If the --disable-api-security flag is used, enforce that 48 // --authenticate-api must also be used. 49 if config.Siad.AllowAPIBind && !config.Siad.AuthenticateAPI { 50 return errors.New("cannot use --disable-api-security without setting an api password") 51 } 52 return nil 53 } 54 55 // processNetAddr adds a ':' to a bare integer, so that it is a proper port 56 // number. 57 func processNetAddr(addr string) string { 58 _, err := strconv.Atoi(addr) 59 if err == nil { 60 return ":" + addr 61 } 62 return addr 63 } 64 65 // processModules makes the modules string lowercase to make checking if a 66 // module in the string easier, and returns an error if the string contains an 67 // invalid module character. 68 func processModules(modules string) (string, error) { 69 modules = strings.ToLower(modules) 70 validModules := "cghmrtwe" 71 invalidModules := modules 72 for _, m := range validModules { 73 invalidModules = strings.Replace(invalidModules, string(m), "", 1) 74 } 75 if len(invalidModules) > 0 { 76 return "", errors.New("Unable to parse --modules flag, unrecognized or duplicate modules: " + invalidModules) 77 } 78 return modules, nil 79 } 80 81 // processProfileFlags checks that the flags given for profiling are valid. 82 func processProfileFlags(profile string) (string, error) { 83 profile = strings.ToLower(profile) 84 validProfiles := "cmt" 85 86 invalidProfiles := profile 87 for _, p := range validProfiles { 88 invalidProfiles = strings.Replace(invalidProfiles, string(p), "", 1) 89 } 90 if len(invalidProfiles) > 0 { 91 return "", errors.New("Unable to parse --profile flags, unrecognized or duplicate flags: " + invalidProfiles) 92 } 93 return profile, nil 94 } 95 96 // processConfig checks the configuration values and performs cleanup on 97 // incorrect-but-allowed values. 98 func processConfig(config Config) (Config, error) { 99 var err1, err2 error 100 config.Siad.APIaddr = processNetAddr(config.Siad.APIaddr) 101 config.Siad.RPCaddr = processNetAddr(config.Siad.RPCaddr) 102 config.Siad.HostAddr = processNetAddr(config.Siad.HostAddr) 103 config.Siad.Modules, err1 = processModules(config.Siad.Modules) 104 config.Siad.Profile, err2 = processProfileFlags(config.Siad.Profile) 105 err3 := verifyAPISecurity(config) 106 err := build.JoinErrors([]error{err1, err2, err3}, ", and ") 107 if err != nil { 108 return Config{}, err 109 } 110 return config, nil 111 } 112 113 // unlockWallet is called on siad startup and attempts to automatically 114 // unlock the wallet with the given password string. 115 func unlockWallet(w modules.Wallet, password string) error { 116 var validKeys []crypto.TwofishKey 117 dicts := []mnemonics.DictionaryID{"english", "german", "japanese"} 118 for _, dict := range dicts { 119 seed, err := modules.StringToSeed(password, dict) 120 if err != nil { 121 continue 122 } 123 validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(seed))) 124 } 125 validKeys = append(validKeys, crypto.TwofishKey(crypto.HashObject(password))) 126 for _, key := range validKeys { 127 if err := w.Unlock(key); err == nil { 128 return nil 129 } 130 } 131 return modules.ErrBadEncryptionKey 132 } 133 134 // startDaemon uses the config parameters to initialize Sia modules and start 135 // siad. 136 func startDaemon(config Config) (err error) { 137 if config.Siad.AuthenticateAPI { 138 password := os.Getenv("SIA_API_PASSWORD") 139 if password != "" { 140 fmt.Println("Using SIA_API_PASSWORD environment variable") 141 config.APIPassword = password 142 } else { 143 // Prompt user for API password. 144 config.APIPassword, err = passwordPrompt("Enter API password: ") 145 if err != nil { 146 return err 147 } 148 if config.APIPassword == "" { 149 return errors.New("password cannot be blank") 150 } 151 } 152 } 153 154 // Print the siad Version and GitRevision 155 fmt.Println("Sia Daemon v" + build.Version) 156 if build.GitRevision == "" { 157 fmt.Println("WARN: compiled without build commit or version. To compile correctly, please use the makefile") 158 } else { 159 fmt.Println("Git Revision " + build.GitRevision) 160 } 161 162 // Install a signal handler that will catch exceptions thrown by mmap'd 163 // files. 164 // NOTE: ideally we would catch SIGSEGV here too, since that signal can 165 // also be thrown by an mmap I/O error. However, SIGSEGV can occur under 166 // other circumstances as well, and in those cases, we will want a full 167 // stack trace. 168 mmapChan := make(chan os.Signal, 1) 169 signal.Notify(mmapChan, syscall.SIGBUS) 170 go func() { 171 <-mmapChan 172 fmt.Println("A fatal I/O exception (SIGBUS) has occurred.") 173 fmt.Println("Please check your disk for errors.") 174 os.Exit(1) 175 }() 176 177 // Print a startup message. 178 fmt.Println("Loading...") 179 loadStart := time.Now() 180 srv, err := NewServer(config) 181 if err != nil { 182 return err 183 } 184 errChan := make(chan error) 185 go func() { 186 errChan <- srv.Serve() 187 }() 188 err = srv.loadModules() 189 if err != nil { 190 return err 191 } 192 193 // listen for kill signals 194 sigChan := make(chan os.Signal, 1) 195 signal.Notify(sigChan, os.Interrupt, os.Kill) 196 197 // Print a 'startup complete' message. 198 startupTime := time.Since(loadStart) 199 fmt.Println("Finished loading in", startupTime.Seconds(), "seconds") 200 201 // wait for Serve to return or for kill signal to be caught 202 err = func() error { 203 select { 204 case err := <-errChan: 205 return err 206 case <-sigChan: 207 fmt.Println("\rCaught stop signal, quitting...") 208 return srv.Close() 209 } 210 }() 211 if err != nil { 212 build.Critical(err) 213 } 214 215 return nil 216 } 217 218 // startDaemonCmd is a passthrough function for startDaemon. 219 func startDaemonCmd(cmd *cobra.Command, _ []string) { 220 var profileCPU, profileMem, profileTrace bool 221 222 profileCPU = strings.Contains(globalConfig.Siad.Profile, "c") 223 profileMem = strings.Contains(globalConfig.Siad.Profile, "m") 224 profileTrace = strings.Contains(globalConfig.Siad.Profile, "t") 225 226 if build.DEBUG { 227 profileCPU = true 228 profileMem = true 229 } 230 231 if profileCPU || profileMem || profileTrace { 232 go profile.StartContinuousProfile(globalConfig.Siad.ProfileDir, profileCPU, profileMem, profileTrace) 233 } 234 235 // Start siad. startDaemon will only return when it is shutting down. 236 err := startDaemon(globalConfig) 237 if err != nil { 238 die(err) 239 } 240 241 // Daemon seems to have closed cleanly. Print a 'closed' mesasge. 242 fmt.Println("Shutdown complete.") 243 }