Exemplo n.º 1
0
Arquivo: cli.go Projeto: kisom/pond
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
	}
}
Exemplo n.º 2
0
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()
}
Exemplo n.º 3
0
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
}
Exemplo n.º 4
0
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
}