| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 | 
							- // Copyright 2014 The Gogs Authors. All rights reserved.
 
- // Use of this source code is governed by a MIT-style
 
- // license that can be found in the LICENSE file.
 
- package ssh
 
- import (
 
- 	"fmt"
 
- 	"io"
 
- 	"io/ioutil"
 
- 	"net"
 
- 	"os"
 
- 	"os/exec"
 
- 	"path/filepath"
 
- 	"strings"
 
- 	"github.com/unknwon/com"
 
- 	"golang.org/x/crypto/ssh"
 
- 	log "unknwon.dev/clog/v2"
 
- 	"gogs.io/gogs/internal/conf"
 
- 	"gogs.io/gogs/internal/db"
 
- )
 
- func cleanCommand(cmd string) string {
 
- 	i := strings.Index(cmd, "git")
 
- 	if i == -1 {
 
- 		return cmd
 
- 	}
 
- 	return cmd[i:]
 
- }
 
- func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
 
- 	for newChan := range chans {
 
- 		if newChan.ChannelType() != "session" {
 
- 			_ = newChan.Reject(ssh.UnknownChannelType, "unknown channel type")
 
- 			continue
 
- 		}
 
- 		ch, reqs, err := newChan.Accept()
 
- 		if err != nil {
 
- 			log.Error("Error accepting channel: %v", err)
 
- 			continue
 
- 		}
 
- 		go func(in <-chan *ssh.Request) {
 
- 			defer func() {
 
- 				_ = ch.Close()
 
- 			}()
 
- 			for req := range in {
 
- 				payload := cleanCommand(string(req.Payload))
 
- 				switch req.Type {
 
- 				case "env":
 
- 					args := strings.Split(strings.Replace(payload, "\x00", "", -1), "\v")
 
- 					if len(args) != 2 {
 
- 						log.Warn("SSH: Invalid env arguments: '%#v'", args)
 
- 						continue
 
- 					}
 
- 					args[0] = strings.TrimLeft(args[0], "\x04")
 
- 					_, _, err := com.ExecCmdBytes("env", args[0]+"="+args[1])
 
- 					if err != nil {
 
- 						log.Error("env: %v", err)
 
- 						return
 
- 					}
 
- 				case "exec":
 
- 					cmdName := strings.TrimLeft(payload, "'()")
 
- 					log.Trace("SSH: Payload: %v", cmdName)
 
- 					args := []string{"serv", "key-" + keyID, "--config=" + conf.CustomConf}
 
- 					log.Trace("SSH: Arguments: %v", args)
 
- 					cmd := exec.Command(conf.AppPath(), args...)
 
- 					cmd.Env = append(os.Environ(), "SSH_ORIGINAL_COMMAND="+cmdName)
 
- 					stdout, err := cmd.StdoutPipe()
 
- 					if err != nil {
 
- 						log.Error("SSH: StdoutPipe: %v", err)
 
- 						return
 
- 					}
 
- 					stderr, err := cmd.StderrPipe()
 
- 					if err != nil {
 
- 						log.Error("SSH: StderrPipe: %v", err)
 
- 						return
 
- 					}
 
- 					input, err := cmd.StdinPipe()
 
- 					if err != nil {
 
- 						log.Error("SSH: StdinPipe: %v", err)
 
- 						return
 
- 					}
 
- 					// FIXME: check timeout
 
- 					if err = cmd.Start(); err != nil {
 
- 						log.Error("SSH: Start: %v", err)
 
- 						return
 
- 					}
 
- 					_ = req.Reply(true, nil)
 
- 					go func() {
 
- 						_, _ = io.Copy(input, ch)
 
- 					}()
 
- 					_, _ = io.Copy(ch, stdout)
 
- 					_, _ = io.Copy(ch.Stderr(), stderr)
 
- 					if err = cmd.Wait(); err != nil {
 
- 						log.Error("SSH: Wait: %v", err)
 
- 						return
 
- 					}
 
- 					_, _ = ch.SendRequest("exit-status", false, []byte{0, 0, 0, 0})
 
- 					return
 
- 				default:
 
- 				}
 
- 			}
 
- 		}(reqs)
 
- 	}
 
- }
 
- func listen(config *ssh.ServerConfig, host string, port int) {
 
- 	listener, err := net.Listen("tcp", host+":"+com.ToStr(port))
 
- 	if err != nil {
 
- 		log.Fatal("Failed to start SSH server: %v", err)
 
- 	}
 
- 	for {
 
- 		// Once a ServerConfig has been configured, connections can be accepted.
 
- 		conn, err := listener.Accept()
 
- 		if err != nil {
 
- 			log.Error("SSH: Error accepting incoming connection: %v", err)
 
- 			continue
 
- 		}
 
- 		// Before use, a handshake must be performed on the incoming net.Conn.
 
- 		// It must be handled in a separate goroutine,
 
- 		// otherwise one user could easily block entire loop.
 
- 		// For example, user could be asked to trust server key fingerprint and hangs.
 
- 		go func() {
 
- 			log.Trace("SSH: Handshaking for %s", conn.RemoteAddr())
 
- 			sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
 
- 			if err != nil {
 
- 				if err == io.EOF {
 
- 					log.Warn("SSH: Handshaking was terminated: %v", err)
 
- 				} else {
 
- 					log.Error("SSH: Error on handshaking: %v", err)
 
- 				}
 
- 				return
 
- 			}
 
- 			log.Trace("SSH: Connection from %s (%s)", sConn.RemoteAddr(), sConn.ClientVersion())
 
- 			// The incoming Request channel must be serviced.
 
- 			go ssh.DiscardRequests(reqs)
 
- 			go handleServerConn(sConn.Permissions.Extensions["key-id"], chans)
 
- 		}()
 
- 	}
 
- }
 
- // Listen starts a SSH server listens on given port.
 
- func Listen(host string, port int, ciphers []string) {
 
- 	config := &ssh.ServerConfig{
 
- 		Config: ssh.Config{
 
- 			Ciphers: ciphers,
 
- 		},
 
- 		PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
 
- 			pkey, err := db.SearchPublicKeyByContent(strings.TrimSpace(string(ssh.MarshalAuthorizedKey(key))))
 
- 			if err != nil {
 
- 				log.Error("SearchPublicKeyByContent: %v", err)
 
- 				return nil, err
 
- 			}
 
- 			return &ssh.Permissions{Extensions: map[string]string{"key-id": com.ToStr(pkey.ID)}}, nil
 
- 		},
 
- 	}
 
- 	keyPath := filepath.Join(conf.Server.AppDataPath, "ssh", "gogs.rsa")
 
- 	if !com.IsExist(keyPath) {
 
- 		if err := os.MkdirAll(filepath.Dir(keyPath), os.ModePerm); err != nil {
 
- 			panic(err)
 
- 		}
 
- 		_, stderr, err := com.ExecCmd(conf.SSH.KeygenPath, "-f", keyPath, "-t", "rsa", "-m", "PEM", "-N", "")
 
- 		if err != nil {
 
- 			panic(fmt.Sprintf("Failed to generate private key: %v - %s", err, stderr))
 
- 		}
 
- 		log.Trace("SSH: New private key is generateed: %s", keyPath)
 
- 	}
 
- 	privateBytes, err := ioutil.ReadFile(keyPath)
 
- 	if err != nil {
 
- 		panic("SSH: Failed to load private key: " + err.Error())
 
- 	}
 
- 	private, err := ssh.ParsePrivateKey(privateBytes)
 
- 	if err != nil {
 
- 		panic("SSH: Failed to parse private key: " + err.Error())
 
- 	}
 
- 	config.AddHostKey(private)
 
- 	go listen(config, host, port)
 
- }
 
 
  |