func (c *cliClient) compose(to *Contact, inReplyTo *InboxMessage) { editor := os.Getenv("EDITOR") if len(editor) == 0 { editor = "vi" } tempDir, err := system.SafeTempDir() if err != nil { c.Printf("%s Failed to get safe temp directory: %s\n", termErrPrefix, err) return } tempFile, err := ioutil.TempFile(tempDir, "pond-cli-") if err != nil { c.Printf("%s Failed to create temp file: %s\n", termErrPrefix, err) return } tempFileName := tempFile.Name() defer func() { os.Remove(tempFileName) }() fmt.Fprintf(tempFile, "# Pond message. Lines prior to the first blank line are ignored.\nTo: %s\n\n", to.name) cmd := exec.Command(editor, tempFileName) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { c.Printf("%s Failed to run editor: %s\n", termErrPrefix, err) return } tempFile.Close() tempFile, err = os.Open(tempFileName) if err != nil { c.Printf("%s Failed to open temp file: %s\n", termErrPrefix, err) return } _, err = ioutil.ReadAll(tempFile) if err != nil { c.Printf("%s Failed to read temp file: %s\n", termErrPrefix, err) return } }
func (c *cliClient) compose(to *Contact, draft *Draft, inReplyTo *InboxMessage) { if draft == nil { draft = &Draft{ id: c.randId(), created: time.Now(), to: to.id, cliId: c.newCliId(), } if inReplyTo != nil && inReplyTo.message != nil { draft.inReplyTo = inReplyTo.message.GetId() draft.body = indentForReply(inReplyTo.message.GetBody()) } c.Printf("%s Created new draft: %s%s%s\n", termInfoPrefix, termCliIdStart, draft.cliId.String(), termReset) c.drafts[draft.id] = draft c.setCurrentObject(draft) } if to == nil { to = c.contacts[draft.to] } if to.isPending { c.Printf("%s Cannot send message to pending contact\n", termErrPrefix) return } tempDir, err := system.SafeTempDir() if err != nil { c.Printf("%s Failed to get safe temp directory: %s\n", termErrPrefix, err) return } tempFile, err := ioutil.TempFile(tempDir, "pond-cli-") if err != nil { c.Printf("%s Failed to create temp file: %s\n", termErrPrefix, err) return } tempFileName := tempFile.Name() defer func() { os.Remove(tempFileName) }() fmt.Fprintf(tempFile, "# Pond message. Lines prior to the first blank line are ignored.\nTo: %s\n\n", to.name) if len(draft.body) == 0 { tempFile.WriteString("\n") } else { tempFile.WriteString(draft.body) } // The editor is forced to vim because I'm not sure about leaks from // other editors. (I'm not sure about leaks from vim either, but at // least I can set some arguments to remove the obvious ones.) cmd := exec.Command( "vim", "-c", "set nobackup", "-c", "set noswapfile", "-c", "set nowritebackup", "-N", "--noplugin", "-X", "--cmd", "set modelines=0", "-c", "set viminfo=", "+4", "--", tempFileName) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { c.Printf("%s Failed to run editor: %s\n", termErrPrefix, err) return } tempFile.Close() tempFile, err = os.Open(tempFileName) if err != nil { c.Printf("%s Failed to open temp file: %s\n", termErrPrefix, err) return } contents, err := ioutil.ReadAll(tempFile) if err != nil { c.Printf("%s Failed to read temp file: %s\n", termErrPrefix, err) return } if i := bytes.Index(contents, []byte("\n\n")); i >= 0 { contents = contents[i+2:] } draft.body = string(contents) c.printDraftSize(draft) c.save() }
func do() bool { if err := system.IsSafe(); err != nil { fmt.Fprintf(os.Stderr, "System checks failed: %s\n", err) return false } editor := os.Getenv("EDITOR") if len(editor) == 0 { fmt.Fprintf(os.Stderr, "$EDITOR is not set\n") return false } stateFile := &disk.StateFile{ Path: *stateFileName, Rand: crypto_rand.Reader, Log: func(format string, args ...interface{}) { fmt.Fprintf(os.Stderr, format, args...) }, } stateLock, err := stateFile.Lock(false /* don't create */) if err != nil { fmt.Fprintf(os.Stderr, "Cannot open state file: %s\n", err) return false } if stateLock == nil { fmt.Fprintf(os.Stderr, "Cannot obtain lock on state file\n") return false } defer stateLock.Close() var state *disk.State var passphrase string for { state, err = stateFile.Read(passphrase) if err == nil { break } if err != disk.BadPasswordError { fmt.Fprintf(os.Stderr, "Failed to decrypt state file: %s\n", err) return false } fmt.Fprintf(os.Stderr, "Passphrase: ") passphraseBytes, err := terminal.ReadPassword(0) fmt.Fprintf(os.Stderr, "\n") if err != nil { fmt.Fprintf(os.Stderr, "Failed to read password\n") return false } passphrase = string(passphraseBytes) } tempDir, err := system.SafeTempDir() if err != nil { fmt.Fprintf(os.Stderr, "Failed to get safe temp directory: %s\n", err) return false } tempFile, err := ioutil.TempFile(tempDir, "pond-editstate-") if err != nil { fmt.Fprintf(os.Stderr, "Failed to create temp file: %s\n", err) return false } tempFileName := tempFile.Name() defer func() { os.Remove(tempFileName) }() signals := make(chan os.Signal, 8) signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) go func() { <-signals println("Caught signal: removing", tempFileName) os.Remove(tempFileName) os.Exit(1) }() entities := serialise(tempFile, state) var newStateSerialized []byte for { cmd := exec.Command(editor, tempFileName) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "Failed to run editor: %s\n", err) return false } tempFile.Close() tempFile, err := os.Open(tempFileName) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open temp file: %s\n", err) return false } newState := new(disk.State) err = parse(newState, tempFile, entities) if err == nil { newStateSerialized, err = proto.Marshal(newState) } if err == nil { break } fmt.Fprintf(os.Stderr, "Error parsing: %s\n", err) fmt.Fprintf(os.Stderr, "Hit enter to edit again, or Ctrl-C to abort\n") var buf [100]byte os.Stdin.Read(buf[:]) } states := make(chan disk.NewState) done := make(chan struct{}) go stateFile.StartWriter(states, done) states <- disk.NewState{newStateSerialized, false} close(states) <-done return true }
func do() bool { if err := system.IsSafe(); err != nil { fmt.Fprintf(os.Stderr, "System checks failed: %s\n", err) return false } editor := os.Getenv("EDITOR") if len(editor) == 0 { fmt.Fprintf(os.Stderr, "$EDITOR is not set\n") return false } stateFile, err := os.Open(*stateFileName) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open state file: %s\n", err) return false } defer stateFile.Close() stateLock, ok := disk.LockStateFile(stateFile) if !ok { fmt.Fprintf(os.Stderr, "Cannot obtain lock on state file\n") return false } defer stateLock.Close() encrypted, err := ioutil.ReadAll(stateFile) if err != nil { fmt.Fprintf(os.Stderr, "Failed to read state file: %s\n", err) return false } salt, ok := disk.GetSCryptSaltFromState(encrypted) if !ok { fmt.Fprintf(os.Stderr, "State file is too short to be valid\n") return false } var state *disk.State var key [32]byte for { state, err = disk.LoadState(encrypted, &key) if err == nil { break } if err != disk.BadPasswordError { fmt.Fprintf(os.Stderr, "Failed to decrypt state file: %s\n", err) return false } fmt.Fprintf(os.Stderr, "Passphrase: ") password, err := terminal.ReadPassword(0) fmt.Fprintf(os.Stderr, "\n") if err != nil { fmt.Fprintf(os.Stderr, "Failed to read password\n") return false } keySlice, err := disk.DeriveKey(string(password), &salt) copy(key[:], keySlice) } tempDir, err := system.SafeTempDir() if err != nil { fmt.Fprintf(os.Stderr, "Failed to get safe temp directory: %s\n", err) return false } tempFile, err := ioutil.TempFile(tempDir, "pond-editstate-") if err != nil { fmt.Fprintf(os.Stderr, "Failed to create temp file: %s\n", err) return false } tempFileName := tempFile.Name() defer func() { os.Remove(tempFileName) }() signals := make(chan os.Signal, 8) signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) go func() { <-signals println("Caught signal: removing", tempFileName) os.Remove(tempFileName) os.Exit(1) }() entities := serialise(tempFile, state) var newStateSerialized []byte for { cmd := exec.Command(editor, tempFileName) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { fmt.Fprintf(os.Stderr, "Failed to run editor: %s\n", err) return false } tempFile.Close() tempFile, err := os.Open(tempFileName) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open temp file: %s\n", err) return false } newState := new(disk.State) err = parse(newState, tempFile, entities) if err == nil { newStateSerialized, err = proto.Marshal(newState) } if err == nil { break } fmt.Fprintf(os.Stderr, "Error parsing: %s\n", err) fmt.Fprintf(os.Stderr, "Hit enter to edit again, or Ctrl-C to abort\n") var buf [100]byte os.Stdin.Read(buf[:]) } states := make(chan []byte) done := make(chan bool) go disk.StateWriter(*stateFileName, &key, &salt, states, done) states <- newStateSerialized close(states) <-done return true }