Пример #1
0
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
}
Пример #2
0
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
}
Пример #3
0
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
}
Пример #4
0
// 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
}
Пример #5
0
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
	}
}
Пример #6
0
// 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
}
Пример #7
0
func (pull Pull) Run(command string, ch ssh.Channel) (uint64, error) {
	return gitShell(ch, ch.Stderr(), command)
}
Пример #8
0
// 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
}
Пример #9
0
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
	}
}
Пример #10
-1
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
}