0

I have a simple scp function that is just a wrapper over the scp cli tool.

type credential struct {
    username string
    password string
    host     string
    port     string
}

func scpFile(filepath, destpath string, c *credential) error {
    cmd := exec.Command("scp", filepath, c.username+"@"+c.host+":"+destpath)

    if err := cmd.Run(); err != nil {
        return err
    }

    fmt.Println("done")
    return nil
}

This works just fine now I want to add the capability of putting in a password the SSH if scp needs it. This is what I came up with

func scpFile(filepath, destpath string, c *credential) error {
    cmd := exec.Command("scp", filepath, c.username+"@"+c.host+":"+destpath)
    stdin, err := cmd.StdinPipe()
    if err != nil {
        return err
    }
    defer stdin.Close()

    if err := cmd.Start(); err != nil {
        return err
    }

    io.WriteString(stdin, c.password+"\n")
    cmd.Wait()
    fmt.Println("done")
    return nil
}

This does not work as the password prompt just hangs there. I tried adding a 1 second sleep before I re write to stdin thinking maybe I was writing the password to fast but did not make a difference.

Rodrigo
  • 2,539
  • 2
  • 26
  • 48
  • 1
    This is because of the nature of the scp program, it [doesn't just read the password normally](http://stackoverflow.com/questions/1340366/how-to-make-ssh-receive-the-password-from-stdin). There are various security considerations, so make sure you look into the details. – nothingmuch Nov 08 '16 at 02:26
  • `scp` is just a utility that uses `ssh` as a transport. Automate it the same way you would ssh, which preferably is with publickey authentication. – JimB Nov 08 '16 at 14:14

1 Answers1

0

So I was able to find a work around by instead of trying to send the password to stdin I create a ssh session and scp a file through the ssh session. Here is the new scpFile function:

func scpFile(filePath, destinationPath string, session *ssh.Session) error {
    defer session.Close()

    f, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer f.Close()

    s, err := f.Stat()
    if err != nil {
        return err
    }

    go func() {
        w, _ := session.StdinPipe()
        defer w.Close()
        fmt.Fprintf(w, "C%#o %d %s\n", s.Mode().Perm(), s.Size(), path.Base(filePath))
        io.Copy(w, f)
        fmt.Fprint(w, "\x00")
    }()
    cmd := fmt.Sprintf("scp -t %s", destinationPath)
    if err := session.Run(cmd); err != nil {
        return err
    }
    return nil
}

This could probably be made better but the main idea is there

Rodrigo
  • 2,539
  • 2
  • 26
  • 48