func attachCmd(cmd *exec.Cmd, ch ssh.Channel) (*sync.WaitGroup, error) { var wg sync.WaitGroup wg.Add(3) stdout, err := cmd.StdoutPipe() if err != nil { return nil, err } stderr, err := cmd.StderrPipe() if err != nil { return nil, err } stdin, err := cmd.StdinPipe() if err != nil { return nil, err } go func() { defer wg.Done() io.Copy(stdin, ch) }() go func() { defer wg.Done() io.Copy(ch.Stderr(), stderr) }() go func() { defer wg.Done() io.Copy(ch, stdout) }() return &wg, nil }
func (s *sshServer) handleRequests(channel ssh.Channel, in <-chan *ssh.Request) error { env := make(map[string]string) for req := range in { switch req.Type { default: log.Printf("unrecognized ssh request type=%q payload=%s wantreply=%t", req.Type, req.Payload, req.WantReply) req.Reply(false, nil) // unhandled; tell them so case "env": var e envReq if err := ssh.Unmarshal(req.Payload, &e); err != nil { req.Reply(false, nil) return err } req.Reply(true, nil) env[string(e.Key)] = string(e.Val) case "exec": var e execReq if err := ssh.Unmarshal(req.Payload, &e); err != nil { req.Reply(false, nil) return err } req.Reply(true, nil) var cmdbuf bytes.Buffer for k, v := range env { cmdbuf.WriteString(k) cmdbuf.WriteByte('=') cmdbuf.WriteString(v) cmdbuf.WriteByte(' ') } cmdbuf.Write(e.Command) log.Printf("Running command %q", cmdbuf.String()) cmd := &packer.RemoteCmd{Command: cmdbuf.String()} cmd.Stdout = channel cmd.Stderr = channel.Stderr() var rc int if err := s.comm.Start(cmd); err != nil { rc = 255 // TODO: What is a better choice here? } else { cmd.Wait() rc = cmd.ExitStatus } channel.CloseWrite() channel.SendRequest("exit-status", false, []byte{0, 0, 0, byte(rc)}) channel.Close() } } return nil }
func (push Push) Run(command string, ch ssh.Channel) (uint64, error) { //TODO make "master" be dynamic code, err := gitShell(ch, ch.Stderr(), command) if err == nil { newCommit := getCommit("master") stream := ch.Stderr() err = deploy.Run(stream, newCommit) if err != nil { return 1, err } } return code, err }
// plumbCommand connects the exec in/output and the channel in/output. // // The sidechannel is for sending errors to logs. func plumbCommand(cmd *exec.Cmd, channel ssh.Channel, sidechannel io.Writer) *sync.WaitGroup { var wg sync.WaitGroup inpipe, _ := cmd.StdinPipe() go func() { io.Copy(inpipe, channel) inpipe.Close() }() cmd.Stdout = channel cmd.Stderr = channel.Stderr() return &wg }
func (server *Server) handleRequest(channel ssh.Channel, requests <-chan *ssh.Request, conn *ssh.ServerConn) { defer func() { err := channel.Close() if err != nil { server.Logger.Errorf("Failed to close SSH Channel from %s due to %s", conn.RemoteAddr().String(), err) } server.Logger.Debugf("Close SSH Channel from %s", conn.RemoteAddr().String()) }() for req := range requests { server.Logger.Debugf("Received new SSH Request (type = %s) from %s", req.Type, conn.RemoteAddr().String()) switch req.Type { case "exec": server.handleExecRequest(channel, req, conn) default: var err error if req.Type == "env" { _, err = channel.Stderr().Write([]byte("error: Pages does not support SendEnv.\n")) } else { _, err = channel.Write([]byte("You've successfully authenticated, but Pages does not provide shell access.\n")) } if err != nil && err != io.EOF { server.Logger.Errorf("Failed to Talk to SSH Request due to %s", err) } err = req.Reply(false, nil) if err != nil && err != io.EOF { server.Logger.Errorf("Failed to Reply false to SSH Request due to %s", err) } err = channel.Close() if err != nil && err != io.EOF { server.Logger.Errorf("Failed to close SSH Request due to %s", err) } server.Logger.Errorf("Close SSH Request due to unsupported SSH Request type: %s", req.Type) } return } }
// answer handles answering requests and channel requests // // Currently, an exec must be either "ping", "git-receive-pack" or // "git-upload-pack". Anything else will result in a failure response. Right // now, we leave the channel open on failure because it is unclear what the // correct behavior for a failed exec is. // // Support for setting environment variables via `env` has been disabled. func (s *server) answer(channel ssh.Channel, requests <-chan *ssh.Request, condata string, sshconn *ssh.ServerConn) error { defer channel.Close() // Answer all the requests on this connection. for req := range requests { ok := false switch req.Type { case "env": o := &EnvVar{} ssh.Unmarshal(req.Payload, o) log.Info("Key='%s', Value='%s'\n", o.Name, o.Value) req.Reply(true, nil) case "exec": clean := cleanExec(req.Payload) parts := strings.SplitN(clean, " ", 2) switch parts[0] { case "ping": err := Ping(channel, req) if err != nil { log.Info("Error pinging: %s", err) } return err case "git-receive-pack", "git-upload-pack": if len(parts) < 2 { log.Info("Expected two-part command.") req.Reply(ok, nil) break } repoName, err := cleanRepoName(parts[1]) if err != nil { log.Err("Illegal repo name: %s.", err) channel.Stderr().Write([]byte("No repo given")) return err } wrapErr := wrapInLock(s.pushLock, repoName, time.Duration(0), s.runReceive(req, sshconn, channel, repoName, parts, condata)) if wrapErr == errAlreadyLocked { log.Info(multiplePush) // The error must be in git format if pktErr := gitPktLine(channel, fmt.Sprintf("ERR %v\n", multiplePush)); pktErr != nil { log.Err("Failed to write to channel: %s", err) } sendExitStatus(1, channel) req.Reply(false, nil) return nil } var xs uint32 if wrapErr != nil { log.Err("Failed git receive: %v", err) xs = 1 } sendExitStatus(xs, channel) return nil default: log.Info("Illegal command is '%s'\n", clean) req.Reply(false, nil) return nil } if err := sendExitStatus(0, channel); err != nil { log.Err("Failed to write exit status: %s", err) } return nil default: // We simply ignore all of the other cases and leave the // channel open to take additional requests. log.Info("Received request of type %s\n", req.Type) req.Reply(false, nil) } } return nil }
func (pull Pull) Run(command string, ch ssh.Channel) (uint64, error) { return gitShell(ch, ch.Stderr(), command) }
// Receive receives a Git repo. // This will only work for git-receive-pack. func Receive( repo, operation, gitHome string, channel ssh.Channel, fingerprint, username, conndata, receivetype string) error { log.Info("receiving git repo name: %s, operation: %s, fingerprint: %s, user: %s", repo, operation, fingerprint, username) if receivetype == "mock" { channel.Write([]byte("OK")) return nil } repoPath := filepath.Join(gitHome, repo) log.Info("creating repo directory %s", repoPath) if _, err := createRepo(repoPath); err != nil { err = fmt.Errorf("Did not create new repo (%s)", err) return err } log.Info("writing pre-receive hook under %s", repoPath) if err := createPreReceiveHook(gitHome, repoPath); err != nil { err = fmt.Errorf("Did not write pre-receive hook (%s)", err) return err } cmd := exec.Command("git-shell", "-c", fmt.Sprintf("%s '%s'", operation, repo)) log.Info(strings.Join(cmd.Args, " ")) var errbuff bytes.Buffer cmd.Dir = gitHome cmd.Env = []string{ fmt.Sprintf("RECEIVE_USER=%s", username), fmt.Sprintf("RECEIVE_REPO=%s", repo), fmt.Sprintf("RECEIVE_FINGERPRINT=%s", fingerprint), fmt.Sprintf("SSH_ORIGINAL_COMMAND=%s '%s'", operation, repo), fmt.Sprintf("SSH_CONNECTION=%s", conndata), } cmd.Env = append(cmd.Env, os.Environ()...) log.Debug("Working Dir: %s", cmd.Dir) log.Debug("Environment: %s", strings.Join(cmd.Env, ",")) inpipe, err := cmd.StdinPipe() if err != nil { return err } cmd.Stdout = channel cmd.Stderr = io.MultiWriter(channel.Stderr(), &errbuff) if err := cmd.Start(); err != nil { err = fmt.Errorf("Failed to start git pre-receive hook: %s (%s)", err, errbuff.Bytes()) return err } if _, err := io.Copy(inpipe, channel); err != nil { err = fmt.Errorf("Failed to write git objects into the git pre-receive hook (%s)", err) return err } fmt.Println("Waiting for git-receive to run.") fmt.Println("Waiting for deploy.") if err := cmd.Wait(); err != nil { err = fmt.Errorf("Failed to run git pre-receive hook: %s (%s)", errbuff.Bytes(), err) return err } if errbuff.Len() > 0 { log.Err("Unreported error: %s", errbuff.Bytes()) return errors.New(errbuff.String()) } log.Info("Deploy complete.") return nil }
func (server *Server) handleExecRequest(channel ssh.Channel, request *ssh.Request, conn *ssh.ServerConn) { doReply := func(ok bool) { err := request.Reply(ok, nil) if err != nil { server.Logger.Errorf("Failed to reply %t to SSH Request from %s due to %s", ok, conn.RemoteAddr().String(), err) } server.Logger.Debugf("Reply to SSH Request `%t` from %s", ok, conn.RemoteAddr().String()) } if len(request.Payload) < 4 { server.Logger.Errorf("Payload must not be shorter than 4 bytes, but only %d bytes", len(request.Payload)) doReply(false) return } header := request.Payload[:4] cmdLen := int64(binary.BigEndian.Uint32(header)) if int64(len(request.Payload)) < 4+cmdLen { server.Logger.Errorf("Payload must not be shorter than %d bytes, but only %d bytes", 4+cmdLen, len(request.Payload)) doReply(false) return } cmd := request.Payload[4 : 4+cmdLen] server.Logger.Debugf("Execute command `%s` via SSH from %s", string(cmd), conn.RemoteAddr().String()) shellCmd := exec.Command(server.Config.ShellPath, "-c", string(cmd)) stdinPipe, err := shellCmd.StdinPipe() if err != nil { server.Logger.Errorf("Failed to create STDIN pipe error for command: %s", err) doReply(false) return } defer stdinPipe.Close() stdoutPipe, err := shellCmd.StdoutPipe() if err != nil { server.Logger.Errorf("Failed to create STDOUT pipe error for command: %s", err) doReply(false) return } defer stdoutPipe.Close() stderrPipe, err := shellCmd.StderrPipe() if err != nil { server.Logger.Errorf("Failed to create STDERR pipe error for command: %s", err) doReply(false) return } defer stderrPipe.Close() sendExitStatus := func() { channel.SendRequest("exit-status", false, []byte{0, 0, 0, 0}) server.Logger.Debugf("Sent exit status 0 to %s", conn.RemoteAddr().String()) } var once sync.Once go func() { io.Copy(stdinPipe, channel) once.Do(sendExitStatus) }() go func() { io.Copy(channel, stdoutPipe) once.Do(sendExitStatus) }() go func() { io.Copy(channel.Stderr(), stderrPipe) once.Do(sendExitStatus) }() err = shellCmd.Start() if err != nil { server.Logger.Errorf("Close SSH Channel from %s due to command error: %s", conn.RemoteAddr().String(), err) doReply(false) return } doReply(true) _, err = shellCmd.Process.Wait() if err != nil { _, ok := err.(*exec.ExitError) if !ok { server.Logger.Errorf("Failed to wait command(PID = %d) due to %s", shellCmd.Process.Pid, err) } return } }
func pipe(ch ssh.Channel, client *ssh.Client, session *ssh.Session, command string) (int, error) { targetStderr, err := session.StderrPipe() if err != nil { return -1, errors.New("fail to pipe stderr: " + err.Error()) } targetStdout, err := session.StdoutPipe() if err != nil { return -1, errors.New("fail to pipe stdout: " + err.Error()) } targetStdin, err := session.StdinPipe() if err != nil { return -1, errors.New("fail to pipe stdin: " + err.Error()) } go io.Copy(targetStdin, ch) go io.Copy(ch.Stderr(), targetStderr) go io.Copy(ch, targetStdout) err = session.Start(command) if err != nil { ch.Write([]byte("Error when starting '" + command + "': " + err.Error())) ch.Close() } err = session.Wait() if err != nil { if err, ok := err.(*ssh.ExitError); ok { return err.ExitStatus(), nil } else { return -1, errors.New("failed to wait ssh command: " + err.Error()) } } return 0, nil }