github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/cmd/lara/helpers.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  
     8  	"github.com/hoffie/larasync/api/client"
     9  	"github.com/hoffie/larasync/helpers/colorhash"
    10  	"github.com/hoffie/larasync/repository"
    11  	"golang.org/x/crypto/ssh/terminal"
    12  )
    13  
    14  const (
    15  	// PrivateKeySize is the size of the key used for signing.
    16  	PrivateKeySize = repository.PrivateKeySize
    17  	// PublicKeySize ist the size of the key used to verify the signature.
    18  	PublicKeySize = repository.PublicKeySize
    19  	// EncryptionKeySize is the key size used for encryption purposes.
    20  	EncryptionKeySize = repository.EncryptionKeySize
    21  )
    22  
    23  // clientFor returns the Client which is configured to communicate
    24  // with the given server repository.
    25  func (d *Dispatcher) clientFor(r *repository.ClientRepository) (*client.Client, error) {
    26  	sc, err := r.StateConfig()
    27  	if err != nil {
    28  		return nil, fmt.Errorf("unable to load state config (%s)", err)
    29  	}
    30  	defaultServer := sc.DefaultServer
    31  	if defaultServer.URL == "" {
    32  		return nil, fmt.Errorf("no default server configured (state)")
    33  	}
    34  	privKey, err := r.GetSigningPrivateKey()
    35  	if err != nil {
    36  		return nil, fmt.Errorf("unable to get signing private key (%s)", err)
    37  	}
    38  	client := d.clientForState(sc)
    39  	client.SetSigningPrivateKey(privKey)
    40  
    41  	return client, nil
    42  }
    43  
    44  func (d *Dispatcher) clientForState(sc *repository.StateConfig) *client.Client {
    45  	d.sc = sc
    46  	defaultServer := sc.DefaultServer
    47  	return client.New(defaultServer.URL, defaultServer.Fingerprint,
    48  		d.confirmFingerprint)
    49  }
    50  
    51  // promptPassword outputs the given prompt text and waits for a value to be entered
    52  // on the input stream. It attempts to do so securely.
    53  func (d *Dispatcher) promptPassword(prompt string) ([]byte, error) {
    54  	switch d.stdin.(type) {
    55  	case *os.File:
    56  		return d.promptGetpass(prompt)
    57  	}
    58  	return d.promptCleartext(prompt)
    59  }
    60  
    61  // promptGetpass reads a password from our input,
    62  // attempting to hide the input if possible.
    63  func (d *Dispatcher) promptGetpass(prompt string) ([]byte, error) {
    64  	file := d.stdin.(*os.File)
    65  	fd := int(file.Fd())
    66  	if !terminal.IsTerminal(fd) {
    67  		return d.promptCleartext(prompt)
    68  	}
    69  	d.stdout.Write([]byte(prompt))
    70  	defer d.stdout.Write([]byte("\n"))
    71  	return terminal.ReadPassword(fd)
    72  }
    73  
    74  // promptCleartext reads text from our input in the standard way.
    75  func (d *Dispatcher) promptCleartext(prompt string) ([]byte, error) {
    76  	d.stdout.Write([]byte(prompt))
    77  	line, err := d.readLine()
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	return line[:len(line)], nil
    82  }
    83  
    84  // readLine reads exactly one line; it does not return the delimiter.
    85  // The difference to other methods for similar goals (bufio.Scanner or ReadBytes)
    86  // is that it only reads as much data as is needed, i.e. it keeps any left-over
    87  // data in the original reader for later access.
    88  func (d *Dispatcher) readLine() ([]byte, error) {
    89  	buf := []byte{}
    90  	oneChar := make([]byte, 1)
    91  	for {
    92  		_, err := d.stdin.Read(oneChar)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		if oneChar[0] == '\n' {
    97  			return buf, nil
    98  		}
    99  		buf = append(buf, oneChar[0])
   100  	}
   101  }
   102  
   103  // confirmFingerprint is called to ask for permission to connect to a new server.
   104  func (d *Dispatcher) confirmFingerprint(fp string) bool {
   105  	fmt.Fprintf(d.stdout, "You are connecting to this server for the first time.\n")
   106  	fmt.Fprintf(d.stdout, "To avoid any attacks on the connection between you and the server,\n")
   107  	fmt.Fprint(d.stdout, "please verify the fingerprint.\n")
   108  	fmt.Fprint(d.stdout, "Do this by getting the fingerprint on the server using\n")
   109  	fmt.Fprint(d.stdout, "  lara server-fingerprint\n")
   110  	fmt.Fprint(d.stdout, "or ask the server administrator to provide you with the fingerprint.\n\n")
   111  	fmt.Fprint(d.stdout, "Only continue connecting when the above mentioned fingerprint matches this value:\n")
   112  	fmt.Fprintf(d.stdout, "\n%s\n%s\n\n", colorhash.Format(fp), fp)
   113  	res, err := d.promptCleartext("Do the fingerprints match? (y/N) ")
   114  	if err != nil {
   115  		return false
   116  	}
   117  	accept := reflect.DeepEqual(res, []byte("y"))
   118  	if !accept {
   119  		return false
   120  	}
   121  	if d.sc == nil {
   122  		fmt.Fprintf(d.stderr,
   123  			"Warning: cannot save fingerprint (no state config)\n")
   124  		// yes, we return true here nevertheless
   125  		return true
   126  	}
   127  	d.sc.DefaultServer.Fingerprint = fp
   128  	err = d.sc.Save()
   129  	if err != nil {
   130  		fmt.Fprintf(d.stderr, "Warning: failed to save fingerprint (%s)\n", err)
   131  	}
   132  	return true
   133  }