/////////////////////////////////////////////////////////////////////////////// // Copyright © 2020 xx network SEZC // // // // Use of this source code is governed by a license that can be found in the // // LICENSE file // /////////////////////////////////////////////////////////////////////////////// // Package cmd initializes the CLI and config parsers as well as the logger. package cmd import ( "encoding/base64" "encoding/binary" "encoding/hex" "encoding/json" "fmt" cryptoE2e "gitlab.com/elixxir/crypto/e2e" "io/ioutil" "log" "os" "runtime/pprof" "strconv" "strings" "sync" "time" "gitlab.com/elixxir/client/backup" "gitlab.com/elixxir/client/xxdk" "gitlab.com/elixxir/client/catalog" "gitlab.com/elixxir/client/e2e/ratchet/partner/session" "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" backupCrypto "gitlab.com/elixxir/crypto/backup" "gitlab.com/elixxir/crypto/contact" "gitlab.com/elixxir/primitives/excludedRounds" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/utils" ) // Key used for storing xxdk.ReceptionIdentity objects const identityStorageKey = "identityStorageKey" // Execute adds all child commands to the root command and sets flags // appropriately. This is called by main.main(). It only needs to // happen once to the rootCmd. func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) } } // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "client", Short: "Runs a client for cMix anonymous communication platform", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { profileOut := viper.GetString(profileCpuFlag) if profileOut != "" { f, err := os.Create(profileOut) if err != nil { jww.FATAL.Panicf("%+v", err) } pprof.StartCPUProfile(f) } cmixParams, e2eParams := initParams() authCbs := makeAuthCallbacks( viper.GetBool(unsafeChannelCreationFlag), e2eParams) user := initE2e(cmixParams, e2eParams, authCbs) jww.INFO.Printf("Client Initialized...") receptionIdentity := user.GetReceptionIdentity() jww.INFO.Printf("User: %s", receptionIdentity.ID) writeContact(receptionIdentity.GetContact()) var recipientContact contact.Contact var recipientID *id.ID destFile := viper.GetString(destFileFlag) destId := viper.GetString(destIdFlag) sendId := viper.GetString(sendIdFlag) if destFile != "" { recipientContact = readContact(destFile) recipientID = recipientContact.ID } else if destId == "0" || sendId == destId { jww.INFO.Printf("Sending message to self") recipientID = receptionIdentity.ID recipientContact = receptionIdentity.GetContact() } else { recipientID = parseRecipient(destId) jww.INFO.Printf("destId: %v\nrecipientId: %v", destId, recipientID) } isPrecanPartner := isPrecanID(recipientID) jww.INFO.Printf("Client: %s, Partner: %s", receptionIdentity.ID, recipientID) user.GetE2E().EnableUnsafeReception() recvCh := registerMessageListener(user) jww.INFO.Printf("Starting Network followers...") err := user.StartNetworkFollower(5 * time.Second) if err != nil { jww.FATAL.Panicf("%+v", err) } jww.INFO.Printf("Network followers started!") // Wait until connected or crash on timeout connected := make(chan bool, 10) user.GetCmix().AddHealthCallback( func(isConnected bool) { connected <- isConnected }) waitUntilConnected(connected) // After connection, make sure we have registered with at least // 85% of the nodes numReg := 1 total := 100 jww.INFO.Printf("Registering with nodes...") for numReg < (total*3)/4 { time.Sleep(1 * time.Second) numReg, total, err = user.GetNodeRegistrationStatus() if err != nil { jww.FATAL.Panicf("%+v", err) } jww.INFO.Printf("Registering with nodes (%d/%d)...", numReg, total) } user.GetBackupContainer().TriggerBackup("Integration test.") jww.INFO.Printf("Client backup triggered...") // Send Messages msgBody := viper.GetString(messageFlag) time.Sleep(10 * time.Second) // Accept auth request for this recipient authConfirmed := false if viper.GetBool(acceptChannelFlag) { // Verify that the confirmation message makes it to the // original sender if viper.GetBool(verifySendFlag) { acceptChannelVerified(user, recipientID, e2eParams) } else { // Accept channel, agnostic of round result acceptChannel(user, recipientID) } // Do not wait for channel confirmations if we // accepted one authConfirmed = true } jww.INFO.Printf("Preexisting E2e partners: %+v", user.GetE2E().GetAllPartnerIDs()) if user.GetE2E().HasAuthenticatedChannel(recipientID) { jww.INFO.Printf("Authenticated channel already in "+ "place for %s", recipientID) authConfirmed = true } else { jww.INFO.Printf("No authenticated channel in "+ "place for %s", recipientID) } // Send unsafe messages or not? unsafe := viper.GetBool(unsafeFlag) sendAuthReq := viper.GetBool(sendAuthRequestFlag) if !unsafe && !authConfirmed && !isPrecanPartner && sendAuthReq { addAuthenticatedChannel(user, recipientID, recipientContact, e2eParams) } else if !unsafe && !authConfirmed && isPrecanPartner { addPrecanAuthenticatedChannel(user, recipientID, recipientContact) authConfirmed = true } else if !unsafe && authConfirmed && !isPrecanPartner && sendAuthReq { jww.WARN.Printf("Resetting negotiated auth channel") resetAuthenticatedChannel(user, recipientID, recipientContact, e2eParams) authConfirmed = false } if !unsafe && !authConfirmed { // Signal for authConfirm callback in a separate thread go func() { for { authID := <-authCbs.confCh if authID.Cmp(recipientID) { authConfirmed = true } } }() jww.INFO.Printf("Waiting for authentication channel"+ " confirmation with partner %s", recipientID) scnt := uint(0) // Wait until authConfirmed waitSecs := viper.GetUint(authTimeoutFlag) for !authConfirmed && scnt < waitSecs { time.Sleep(1 * time.Second) scnt++ } if scnt == waitSecs { jww.FATAL.Panicf("Could not confirm "+ "authentication channel for %s, "+ "waited %d seconds.", recipientID, waitSecs) } jww.INFO.Printf("Authentication channel confirmation"+ " took %d seconds", scnt) jww.INFO.Printf("Authenticated partners saved: %v\n PartnersList: %+v", !user.GetStorage().GetKV().IsMemStore(), user.GetE2E().GetAllPartnerIDs()) } // DeleteFingerprint this recipient if viper.GetBool(deleteChannelFlag) { deleteChannel(user, recipientID) } if viper.GetBool(deleteReceiveRequestsFlag) { err = user.GetAuth().DeleteReceiveRequests() if err != nil { jww.FATAL.Panicf("Failed to delete received requests:"+ " %+v", err) } } if viper.GetBool(deleteSentRequestsFlag) { err = user.GetAuth().DeleteSentRequests() if err != nil { jww.FATAL.Panicf("Failed to delete sent requests:"+ " %+v", err) } } if viper.GetBool(deleteAllRequestsFlag) { err = user.GetAuth().DeleteAllRequests() if err != nil { jww.FATAL.Panicf("Failed to delete all requests:"+ " %+v", err) } } if viper.GetBool(deleteRequestFlag) { err = user.GetAuth().DeleteRequest(recipientID) if err != nil { jww.FATAL.Panicf("Failed to delete request for %s:"+ " %+v", recipientID, err) } } mt := catalog.MessageType(catalog.XxMessage) payload := []byte(msgBody) recipient := recipientID jww.INFO.Printf("Client Sending messages...") wg := &sync.WaitGroup{} sendCnt := int(viper.GetUint(sendCountFlag)) wg.Add(sendCnt) go func() { sendDelay := time.Duration(viper.GetUint(sendDelayFlag)) for i := 0; i < sendCnt; i++ { go func(i int) { defer wg.Done() fmt.Printf("Sending to %s: %s\n", recipientID, msgBody) for { // Send messages var roundIDs []id.Round if unsafe { e2eParams.Base.DebugTag = "cmd.Unsafe" roundIDs, _, err = user.GetE2E().SendUnsafe( mt, recipient, payload, e2eParams.Base) } else { e2eParams.Base.DebugTag = "cmd.E2E" var sendReport cryptoE2e.SendReport sendReport, err = user.GetE2E().SendE2E(mt, recipient, payload, e2eParams.Base) roundIDs = sendReport.RoundList } if err != nil { jww.FATAL.Panicf("%+v", err) } // Verify message sends were successful if viper.GetBool(verifySendFlag) { if !verifySendSuccess(user, e2eParams.Base, roundIDs, recipientID, payload) { continue } } break } }(i) time.Sleep(sendDelay * time.Millisecond) } }() // Wait until message timeout or we receive enough then exit // TODO: Actually check for how many messages we've received expectedCnt := viper.GetUint(receiveCountFlag) receiveCnt := uint(0) waitSecs := viper.GetUint(waitTimeoutFlag) waitTimeout := time.Duration(waitSecs) * time.Second done := false jww.INFO.Printf("Client receiving messages...") for !done && expectedCnt != 0 { timeoutTimer := time.NewTimer(waitTimeout) select { case <-timeoutTimer.C: fmt.Println("Timed out!") jww.ERROR.Printf("Timed out on message reception after %s!", waitTimeout) done = true break case m := <-recvCh: strToPrint := string(m.Payload) if m.MessageType != catalog.XxMessage { strToPrint = fmt.Sprintf("type is %s", m.MessageType) } else { receiveCnt++ } fmt.Printf("Message received: %s\n", strToPrint) // fmt.Printf("%s", m.Timestamp) if receiveCnt == expectedCnt { done = true break } } } // wait an extra 5 seconds to make sure no messages were missed done = false waitTime := 5 * time.Second if expectedCnt == 0 { // Wait longer if we didn't expect to receive anything waitTime = 15 * time.Second } timer := time.NewTimer(waitTime) for !done { select { case <-timer.C: done = true break case m := <-recvCh: fmt.Printf("Message received: %s\n", string( m.Payload)) // fmt.Printf("%s", m.Timestamp) receiveCnt++ } } jww.INFO.Printf("Received %d/%d Messages!", receiveCnt, expectedCnt) fmt.Printf("Received %d\n", receiveCnt) if roundsNotepad != nil { roundsNotepad.INFO.Printf("\n%s", user.GetCmix().GetVerboseRounds()) } wg.Wait() err = user.StopNetworkFollower() if err != nil { jww.WARN.Printf( "Failed to cleanly close threads: %+v\n", err) } if profileOut != "" { pprof.StopCPUProfile() } jww.INFO.Printf("Client exiting!") }, } func initParams() (xxdk.CMIXParams, xxdk.E2EParams) { e2eParams := xxdk.GetDefaultE2EParams() e2eParams.Session.MinKeys = uint16(viper.GetUint(e2eMinKeysFlag)) e2eParams.Session.MaxKeys = uint16(viper.GetUint(e2eMaxKeysFlag)) e2eParams.Session.NumRekeys = uint16(viper.GetUint(e2eNumReKeysFlag)) e2eParams.Session.RekeyThreshold = viper.GetFloat64(e2eRekeyThresholdFlag) if viper.GetBool(splitSendsFlag) { e2eParams.Base.ExcludedRounds = excludedRounds.NewSet() } cmixParams := xxdk.GetDefaultCMixParams() cmixParams.Network.Pickup.ForceHistoricalRounds = viper.GetBool( forceHistoricalRoundsFlag) cmixParams.Network.FastPolling = !viper.GetBool(slowPollingFlag) cmixParams.Network.Pickup.ForceMessagePickupRetry = viper.GetBool( forceMessagePickupRetryFlag) if cmixParams.Network.Pickup.ForceMessagePickupRetry { period := 3 * time.Second jww.INFO.Printf("Setting Uncheck Round Period to %v", period) cmixParams.Network.Pickup.UncheckRoundPeriod = period } cmixParams.Network.VerboseRoundTracking = viper.GetBool( verboseRoundTrackingFlag) return cmixParams, e2eParams } // initE2e returns a fully-formed xxdk.E2e object func initE2e(cmixParams xxdk.CMIXParams, e2eParams xxdk.E2EParams, callbacks *authCallbacks) *xxdk.E2e { initLog(viper.GetUint(logLevelFlag), viper.GetString(logFlag)) jww.INFO.Printf(Version()) // Intake parameters for user initialization precanId := viper.GetUint(sendIdFlag) protoUserPath := viper.GetString(protoUserPathFlag) userIdPrefix := viper.GetString(userIdPrefixFlag) backupPath := viper.GetString(backupInFlag) backupPass := viper.GetString(backupPassFlag) storePassword := parsePassword(viper.GetString(passwordFlag)) storeDir := viper.GetString(sessionFlag) regCode := viper.GetString(regCodeFlag) forceLegacy := viper.GetBool(forceLegacyFlag) jww.DEBUG.Printf("sessionDir: %v", storeDir) // Initialize the user of the proper type var user *xxdk.E2e if precanId != 0 { user = loadOrInitPrecan(precanId, storePassword, storeDir, cmixParams, e2eParams, callbacks) } else if protoUserPath != "" { user = loadOrInitProto(protoUserPath, storePassword, storeDir, cmixParams, e2eParams, callbacks) } else if userIdPrefix != "" { user = loadOrInitVanity(storePassword, storeDir, regCode, userIdPrefix, cmixParams, e2eParams, callbacks) } else if backupPath != "" { user = loadOrInitBackup(backupPath, backupPass, storePassword, storeDir, cmixParams, e2eParams, callbacks) } else { user = loadOrInitUser(forceLegacy, storePassword, storeDir, regCode, cmixParams, e2eParams, callbacks) } // Handle protoUser output if protoUser := viper.GetString(protoUserOutFlag); protoUser != "" { jsonBytes, err := user.ConstructProtoUserFile() if err != nil { jww.FATAL.Panicf("cannot construct proto user file: %v", err) } err = utils.WriteFileDef(protoUser, jsonBytes) if err != nil { jww.FATAL.Panicf("cannot write proto user to file: %v", err) } } // Handle backup output if backupOut := viper.GetString(backupOutFlag); backupOut != "" { if !forceLegacy { jww.FATAL.Panicf("Unable to make backup for non-legacy sender!") } updateBackupCb := func(encryptedBackup []byte) { jww.INFO.Printf("Backup update received, size %d", len(encryptedBackup)) fmt.Println("Backup update received.") err := utils.WriteFileDef(backupOut, encryptedBackup) if err != nil { jww.FATAL.Panicf("cannot write backup: %+v", err) } backupJsonPath := viper.GetString(backupJsonOutFlag) if backupJsonPath != "" { var b backupCrypto.Backup err = b.Decrypt(backupPass, encryptedBackup) if err != nil { jww.ERROR.Printf("cannot decrypt backup: %+v", err) } backupJson, err := json.Marshal(b) if err != nil { jww.ERROR.Printf("Failed to JSON unmarshal backup: %+v", err) } err = utils.WriteFileDef(backupJsonPath, backupJson) if err != nil { jww.FATAL.Panicf("Failed to write backup to file: %+v", err) } } } _, err := backup.InitializeBackup(backupPass, updateBackupCb, user.GetBackupContainer(), user.GetE2E(), user.GetStorage(), nil, user.GetStorage().GetKV(), user.GetRng()) if err != nil { jww.FATAL.Panicf("Failed to initialize backup with key %q: %+v", backupPass, err) } } return user } func acceptChannel(user *xxdk.E2e, recipientID *id.ID) id.Round { recipientContact, err := user.GetAuth().GetReceivedRequest( recipientID) if err != nil { jww.FATAL.Panicf("%+v", err) } rid, err := user.GetAuth().Confirm( recipientContact) if err != nil { jww.FATAL.Panicf("%+v", err) } return rid } func deleteChannel(user *xxdk.E2e, partnerId *id.ID) { err := user.DeleteContact(partnerId) if err != nil { jww.FATAL.Panicf("%+v", err) } } func addAuthenticatedChannel(user *xxdk.E2e, recipientID *id.ID, recipient contact.Contact, e2eParams xxdk.E2EParams) { var allowed bool if viper.GetBool(unsafeChannelCreationFlag) { msg := "unsafe channel creation enabled\n" jww.WARN.Printf(msg) fmt.Printf("WARNING: %s", msg) allowed = true } else { allowed = askToCreateChannel(recipientID) } if !allowed { jww.FATAL.Panicf("User did not allow channel creation!") } msg := fmt.Sprintf("Adding authenticated channel for: %s\n", recipientID) jww.INFO.Printf(msg) fmt.Printf(msg) recipientContact := recipient if recipientContact.ID != nil && recipientContact.DhPubKey != nil { me := user.GetReceptionIdentity().GetContact() jww.INFO.Printf("Requesting auth channel from: %s", recipientID) // Verify that the auth request makes it to the recipient // by monitoring the round result if viper.GetBool(verifySendFlag) { requestChannelVerified(user, recipientContact, me, e2eParams) } else { // Just call Request, agnostic of round result _, err := user.GetAuth().Request(recipientContact, me.Facts) if err != nil { jww.FATAL.Panicf("%+v", err) } } } else { jww.ERROR.Printf("Could not add auth channel for %s", recipientID) } } func resetAuthenticatedChannel(user *xxdk.E2e, recipientID *id.ID, recipient contact.Contact, e2eParams xxdk.E2EParams) { var allowed bool if viper.GetBool(unsafeChannelCreationFlag) { msg := "unsafe channel creation enabled\n" jww.WARN.Printf(msg) fmt.Printf("WARNING: %s", msg) allowed = true } else { allowed = askToCreateChannel(recipientID) } if !allowed { jww.FATAL.Panicf("User did not allow channel reset!") } msg := fmt.Sprintf("Resetting authenticated channel for: %s\n", recipientID) jww.INFO.Printf(msg) fmt.Printf(msg) recipientContact := recipient if recipientContact.ID != nil && recipientContact.DhPubKey != nil { jww.INFO.Printf("Requesting auth channel from: %s", recipientID) // Verify that the auth request makes it to the recipient // by monitoring the round result if viper.GetBool(verifySendFlag) { resetChannelVerified(user, recipientContact, e2eParams) } else { _, err := user.GetAuth().Reset(recipientContact) if err != nil { jww.FATAL.Panicf("%+v", err) } } } else { jww.ERROR.Printf("Could not reset auth channel for %s", recipientID) } } func acceptChannelVerified(user *xxdk.E2e, recipientID *id.ID, params xxdk.E2EParams) { roundTimeout := params.Base.CMIXParams.SendTimeout done := make(chan struct{}, 1) retryChan := make(chan struct{}, 1) for { rid := acceptChannel(user, recipientID) // Monitor rounds for results err := user.GetCmix().GetRoundResults(roundTimeout, makeVerifySendsCallback(retryChan, done), rid) if err != nil { jww.DEBUG.Printf("Could not verify "+ "confirmation message for relationship with %s were sent "+ "successfully, resending messages...", recipientID) continue } select { case <-retryChan: // On a retry, go to the top of the loop jww.DEBUG.Printf("Confirmation message for relationship"+ " with %s were not sent successfully, resending "+ "messages...", recipientID) continue case <-done: // Close channels on verification success close(done) close(retryChan) break } break } } func requestChannelVerified(user *xxdk.E2e, recipientContact, me contact.Contact, params xxdk.E2EParams) { roundTimeout := params.Base.CMIXParams.SendTimeout retryChan := make(chan struct{}, 1) done := make(chan struct{}, 1) for { rid, err := user.GetAuth().Request(recipientContact, me.Facts) if err != nil { continue } // Monitor rounds for results err = user.GetCmix().GetRoundResults(roundTimeout, makeVerifySendsCallback(retryChan, done), rid) if err != nil { jww.DEBUG.Printf("Could not verify auth request was sent " + "successfully, resending...") continue } select { case <-retryChan: // On a retry, go to the top of the loop jww.DEBUG.Printf("Auth request was not sent " + "successfully, resending...") continue case <-done: // Close channels on verification success close(done) close(retryChan) break } break } } func resetChannelVerified(user *xxdk.E2e, recipientContact contact.Contact, params xxdk.E2EParams) { roundTimeout := params.Base.CMIXParams.SendTimeout retryChan := make(chan struct{}, 1) done := make(chan struct{}, 1) for { rid, err := user.GetAuth().Reset(recipientContact) if err != nil { jww.FATAL.Panicf("%+v", err) } // Monitor rounds for results err = user.GetCmix().GetRoundResults(roundTimeout, makeVerifySendsCallback(retryChan, done), rid) if err != nil { jww.DEBUG.Printf("Could not verify auth request was sent " + "successfully, resending...") continue } select { case <-retryChan: // On a retry, go to the top of the loop jww.DEBUG.Printf("Auth request was not sent " + "successfully, resending...") continue case <-done: // Close channels on verification success close(done) close(retryChan) break } break } } func waitUntilConnected(connected chan bool) { waitTimeout := time.Duration(viper.GetUint(waitTimeoutFlag)) timeoutTimer := time.NewTimer(waitTimeout * time.Second) isConnected := false // Wait until we connect or panic if we can't by a timeout for !isConnected { select { case isConnected = <-connected: jww.INFO.Printf("Network Status: %v\n", isConnected) break case <-timeoutTimer.C: jww.FATAL.Panicf("timeout on connection after %s", waitTimeout*time.Second) } } // Now start a thread to empty this channel and update us // on connection changes for debugging purposes. go func() { prev := true for { select { case isConnected = <-connected: if isConnected != prev { prev = isConnected jww.INFO.Printf( "Network Status Changed: %v\n", isConnected) } break } } }() } func parseRecipient(idStr string) *id.ID { if idStr == "0" { jww.FATAL.Panicf("No recipient specified") } if strings.HasPrefix(idStr, "0x") { return getUIDFromHexString(idStr[2:]) } else if strings.HasPrefix(idStr, "b64:") { return getUIDFromb64String(idStr[4:]) } else { return getUIDFromString(idStr) } } func getUIDFromHexString(idStr string) *id.ID { idBytes, err := hex.DecodeString(fmt.Sprintf("%0*d%s", 66-len(idStr), 0, idStr)) if err != nil { jww.FATAL.Panicf("%+v", err) } ID, err := id.Unmarshal(idBytes) if err != nil { jww.FATAL.Panicf("%+v", err) } return ID } func getUIDFromb64String(idStr string) *id.ID { idBytes, err := base64.StdEncoding.DecodeString(idStr) if err != nil { jww.FATAL.Panicf("%+v", err) } ID, err := id.Unmarshal(idBytes) if err != nil { jww.FATAL.Panicf("%+v", err) } return ID } func getPWFromHexString(pwStr string) []byte { pwBytes, err := hex.DecodeString(fmt.Sprintf("%0*d%s", 66-len(pwStr), 0, pwStr)) if err != nil { jww.FATAL.Panicf("%+v", err) } return pwBytes } func getPWFromb64String(pwStr string) []byte { pwBytes, err := base64.StdEncoding.DecodeString(pwStr) if err != nil { jww.FATAL.Panicf("%+v", err) } return pwBytes } func getUIDFromString(idStr string) *id.ID { idInt, err := strconv.Atoi(idStr) if err != nil { jww.FATAL.Panicf("%+v", err) } if idInt > 255 { jww.FATAL.Panicf("cannot convert integers above 255. Use 0x " + "or b64: representation") } idBytes := make([]byte, 33) binary.BigEndian.PutUint64(idBytes, uint64(idInt)) idBytes[32] = byte(id.User) ID, err := id.Unmarshal(idBytes) if err != nil { jww.FATAL.Panicf("%+v", err) } return ID } func initLog(threshold uint, logPath string) { if logPath != "-" && logPath != "" { // Disable stdout output jww.SetStdoutOutput(ioutil.Discard) // Use log file logOutput, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic(err.Error()) } jww.SetLogOutput(logOutput) } if threshold > 1 { jww.INFO.Printf("log level set to: TRACE") jww.SetStdoutThreshold(jww.LevelTrace) jww.SetLogThreshold(jww.LevelTrace) jww.SetFlags(log.LstdFlags | log.Lmicroseconds) } else if threshold == 1 { jww.INFO.Printf("log level set to: DEBUG") jww.SetStdoutThreshold(jww.LevelDebug) jww.SetLogThreshold(jww.LevelDebug) jww.SetFlags(log.LstdFlags | log.Lmicroseconds) } else { jww.INFO.Printf("log level set to: INFO") jww.SetStdoutThreshold(jww.LevelInfo) jww.SetLogThreshold(jww.LevelInfo) } if viper.GetBool(verboseRoundTrackingFlag) { initRoundLog(logPath) } } func askToCreateChannel(recipientID *id.ID) bool { for { fmt.Printf("This is the first time you have messaged %v, "+ "are you sure? (yes/no) ", recipientID) var input string fmt.Scanln(&input) if input == "yes" { return true } if input == "no" { return false } fmt.Printf("Please answer 'yes' or 'no'\n") } } // this the the nodepad used for round logging. var roundsNotepad *jww.Notepad // initRoundLog creates the log output for round tracking. In debug mode, // the client will keep track of all rounds it evaluates if it has // messages in, and then will dump them to this log on client exit func initRoundLog(logPath string) { parts := strings.Split(logPath, ".") path := parts[0] + "-rounds." + parts[1] logOutput, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { jww.FATAL.Panicf(err.Error()) } roundsNotepad = jww.NewNotepad(jww.LevelInfo, jww.LevelInfo, ioutil.Discard, logOutput, "", log.Ldate|log.Ltime) } // init is the initialization function for Cobra which defines commands // and flags. func init() { // NOTE: The point of init() is to be declarative. There is // one init in each sub command. Do not put variable // declarations here, and ensure all the Flags are of the *P // variety, unless there's a very good reason not to have them // as local params to sub command." cobra.OnInitialize(initConfig) // Here you will define your flags and configuration settings. // Cobra supports persistent flags, which, if defined here, // will be global for your application. rootCmd.PersistentFlags().UintP(logLevelFlag, "v", 0, "Verbose mode for debugging") viper.BindPFlag(logLevelFlag, rootCmd.PersistentFlags(). Lookup(logLevelFlag)) rootCmd.PersistentFlags().Bool(verboseRoundTrackingFlag, false, "Verbose round tracking, keeps track and prints all rounds the "+ "client was aware of while running. Defaults to false if not set.") viper.BindPFlag(verboseRoundTrackingFlag, rootCmd.PersistentFlags().Lookup( verboseRoundTrackingFlag)) rootCmd.PersistentFlags().StringP(sessionFlag, "s", "", "Sets the initial storage directory for "+ "client session data") viper.BindPFlag(sessionFlag, rootCmd.PersistentFlags().Lookup(sessionFlag)) rootCmd.PersistentFlags().StringP(writeContactFlag, "w", "-", "Write contact information, if any, to this file, "+ " defaults to stdout") viper.BindPFlag(writeContactFlag, rootCmd.PersistentFlags().Lookup( writeContactFlag)) rootCmd.PersistentFlags().StringP(passwordFlag, "p", "", "Password to the session file") viper.BindPFlag(passwordFlag, rootCmd.PersistentFlags().Lookup( passwordFlag)) rootCmd.PersistentFlags().StringP(ndfFlag, "n", "ndf.json", "Path to the network definition JSON file") viper.BindPFlag(ndfFlag, rootCmd.PersistentFlags().Lookup(ndfFlag)) rootCmd.PersistentFlags().StringP(logFlag, "l", "-", "Path to the log output path (- is stdout)") viper.BindPFlag(logFlag, rootCmd.PersistentFlags().Lookup(logFlag)) rootCmd.Flags().StringP(regCodeFlag, "", "", "ReceptionIdentity code (optional)") viper.BindPFlag(regCodeFlag, rootCmd.Flags().Lookup(regCodeFlag)) rootCmd.PersistentFlags().StringP(messageFlag, "m", "", "Message to send") viper.BindPFlag(messageFlag, rootCmd.PersistentFlags().Lookup(messageFlag)) rootCmd.Flags().UintP(sendIdFlag, "", 0, "Use precanned user id (must be between 1 and 40, inclusive)") viper.BindPFlag(sendIdFlag, rootCmd.Flags().Lookup(sendIdFlag)) rootCmd.Flags().StringP(destIdFlag, "d", "0", "ID to send message to (if below 40, will be precanned. Use "+ "'0x' or 'b64:' for hex and base64 representations)") viper.BindPFlag(destIdFlag, rootCmd.Flags().Lookup(destIdFlag)) rootCmd.PersistentFlags().Bool("force-legacy", false, "Force client to operate using legacy identities.") viper.BindPFlag("force-legacy", rootCmd.PersistentFlags().Lookup("force-legacy")) rootCmd.PersistentFlags().StringP(destFileFlag, "", "", "Read this contact file for the destination id") viper.BindPFlag(destFileFlag, rootCmd.PersistentFlags().Lookup(destFileFlag)) rootCmd.PersistentFlags().UintP(sendCountFlag, "", 1, "The number of times to send the message") viper.BindPFlag(sendCountFlag, rootCmd.PersistentFlags().Lookup(sendCountFlag)) rootCmd.PersistentFlags().UintP(sendDelayFlag, "", 500, "The delay between sending the messages in ms") viper.BindPFlag(sendDelayFlag, rootCmd.PersistentFlags().Lookup(sendDelayFlag)) rootCmd.Flags().BoolP(splitSendsFlag, "", false, "Force sends to go over multiple rounds if possible") viper.BindPFlag(splitSendsFlag, rootCmd.Flags().Lookup(splitSendsFlag)) rootCmd.Flags().BoolP(verifySendFlag, "", false, "Ensure successful message sending by checking for round completion") viper.BindPFlag(verifySendFlag, rootCmd.Flags().Lookup(verifySendFlag)) rootCmd.PersistentFlags().UintP(receiveCountFlag, "", 1, "How many messages we should wait for before quitting") viper.BindPFlag(receiveCountFlag, rootCmd.PersistentFlags().Lookup(receiveCountFlag)) rootCmd.PersistentFlags().UintP(waitTimeoutFlag, "", 15, "The number of seconds to wait for messages to arrive") viper.BindPFlag(waitTimeoutFlag, rootCmd.PersistentFlags().Lookup(waitTimeoutFlag)) rootCmd.Flags().BoolP(unsafeFlag, "", false, "Send raw, unsafe messages without e2e encryption.") viper.BindPFlag(unsafeFlag, rootCmd.Flags().Lookup(unsafeFlag)) rootCmd.PersistentFlags().BoolP(unsafeChannelCreationFlag, "", false, "Turns off the user identity authenticated channel check, "+ "automatically approving authenticated channels") viper.BindPFlag(unsafeChannelCreationFlag, rootCmd.PersistentFlags().Lookup(unsafeChannelCreationFlag)) rootCmd.Flags().BoolP(acceptChannelFlag, "", false, "Accept the channel request for the corresponding recipient ID") viper.BindPFlag(acceptChannelFlag, rootCmd.Flags().Lookup(acceptChannelFlag)) rootCmd.PersistentFlags().Bool(deleteChannelFlag, false, "DeleteFingerprint the channel information for the corresponding recipient ID") viper.BindPFlag(deleteChannelFlag, rootCmd.PersistentFlags().Lookup(deleteChannelFlag)) rootCmd.PersistentFlags().Bool(deleteReceiveRequestsFlag, false, "DeleteFingerprint the all received contact requests.") viper.BindPFlag(deleteReceiveRequestsFlag, rootCmd.PersistentFlags().Lookup(deleteReceiveRequestsFlag)) rootCmd.PersistentFlags().Bool(deleteSentRequestsFlag, false, "DeleteFingerprint the all sent contact requests.") viper.BindPFlag(deleteSentRequestsFlag, rootCmd.PersistentFlags().Lookup(deleteSentRequestsFlag)) rootCmd.PersistentFlags().Bool(deleteAllRequestsFlag, false, "DeleteFingerprint the all contact requests, both sent and received.") viper.BindPFlag(deleteAllRequestsFlag, rootCmd.PersistentFlags().Lookup(deleteAllRequestsFlag)) rootCmd.PersistentFlags().Bool(deleteRequestFlag, false, "DeleteFingerprint the request for the specified ID given by the "+ "destfile flag's contact file.") viper.BindPFlag(deleteRequestFlag, rootCmd.PersistentFlags().Lookup(deleteRequestFlag)) rootCmd.Flags().BoolP(sendAuthRequestFlag, "", false, "Send an auth request to the specified destination and wait"+ "for confirmation") viper.BindPFlag(sendAuthRequestFlag, rootCmd.Flags().Lookup(sendAuthRequestFlag)) rootCmd.Flags().UintP(authTimeoutFlag, "", 60, "The number of seconds to wait for an authentication channel"+ "to confirm") viper.BindPFlag(authTimeoutFlag, rootCmd.Flags().Lookup(authTimeoutFlag)) rootCmd.Flags().BoolP(forceHistoricalRoundsFlag, "", false, "Force all rounds to be sent to historical round retrieval") viper.BindPFlag(forceHistoricalRoundsFlag, rootCmd.Flags().Lookup(forceHistoricalRoundsFlag)) // Network params rootCmd.Flags().BoolP(slowPollingFlag, "", false, "Enables polling for unfiltered network updates with RSA signatures") viper.BindPFlag(slowPollingFlag, rootCmd.Flags().Lookup(slowPollingFlag)) rootCmd.Flags().Bool(forceMessagePickupRetryFlag, false, "Enable a mechanism which forces a 50% chance of no message pickup, "+ "instead triggering the message pickup retry mechanism") viper.BindPFlag(forceMessagePickupRetryFlag, rootCmd.Flags().Lookup(forceMessagePickupRetryFlag)) // E2E Params defaultE2EParams := session.GetDefaultParams() rootCmd.Flags().UintP(e2eMinKeysFlag, "", uint(defaultE2EParams.MinKeys), "Minimum number of keys used before requesting rekey") viper.BindPFlag(e2eMinKeysFlag, rootCmd.Flags().Lookup(e2eMinKeysFlag)) rootCmd.Flags().UintP(e2eMaxKeysFlag, "", uint(defaultE2EParams.MaxKeys), "Max keys used before blocking until a rekey completes") viper.BindPFlag(e2eMaxKeysFlag, rootCmd.Flags().Lookup(e2eMaxKeysFlag)) rootCmd.Flags().UintP(e2eNumReKeysFlag, "", uint(defaultE2EParams.NumRekeys), "Number of rekeys reserved for rekey operations") viper.BindPFlag(e2eNumReKeysFlag, rootCmd.Flags().Lookup(e2eNumReKeysFlag)) rootCmd.Flags().Float64P(e2eRekeyThresholdFlag, "", defaultE2EParams.RekeyThreshold, "Number between 0 an 1. Percent of keys used before a rekey is started") viper.BindPFlag(e2eRekeyThresholdFlag, rootCmd.Flags().Lookup(e2eRekeyThresholdFlag)) rootCmd.Flags().String(profileCpuFlag, "", "Enable cpu profiling to this file") viper.BindPFlag(profileCpuFlag, rootCmd.Flags().Lookup(profileCpuFlag)) // Proto user flags rootCmd.Flags().String(protoUserPathFlag, "", "Path to proto user JSON file containing cryptographic primitives "+ "the client will load") viper.BindPFlag(protoUserPathFlag, rootCmd.Flags().Lookup(protoUserPathFlag)) rootCmd.Flags().String(protoUserOutFlag, "", "Path to which a normally constructed client "+ "will write proto user JSON file") viper.BindPFlag(protoUserOutFlag, rootCmd.Flags().Lookup(protoUserOutFlag)) // Backup flags rootCmd.Flags().String(backupOutFlag, "", "Path to output encrypted client backup. If no path is supplied, the "+ "backup system is not started.") viper.BindPFlag(backupOutFlag, rootCmd.Flags().Lookup(backupOutFlag)) rootCmd.Flags().String(backupJsonOutFlag, "", "Path to output unencrypted client JSON backup.") viper.BindPFlag(backupJsonOutFlag, rootCmd.Flags().Lookup(backupJsonOutFlag)) rootCmd.Flags().String(backupInFlag, "", "Path to load backup client from") viper.BindPFlag(backupInFlag, rootCmd.Flags().Lookup(backupInFlag)) rootCmd.Flags().String(backupPassFlag, "", "Passphrase to encrypt/decrypt backup") viper.BindPFlag(backupPassFlag, rootCmd.Flags().Lookup(backupPassFlag)) rootCmd.Flags().String(backupIdListFlag, "", "JSON file containing the backed up partner IDs") viper.BindPFlag(backupIdListFlag, rootCmd.Flags().Lookup(backupIdListFlag)) } // initConfig reads in config file and ENV variables if set. func initConfig() {}